@mono-agent/agent-app 0.4.0 → 0.4.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/README.md +15 -5
- package/dist/adapter-send-tools-main.js +2 -2
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +72 -6
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +413 -38
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-config.d.ts +48 -11
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +98 -24
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +10 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +659 -48
- package/dist/app.js.map +1 -1
- package/dist/artifact-retention.d.ts +32 -0
- package/dist/artifact-retention.d.ts.map +1 -0
- package/dist/artifact-retention.js +111 -0
- package/dist/artifact-retention.js.map +1 -0
- package/dist/audit-runs.d.ts +12 -0
- package/dist/audit-runs.d.ts.map +1 -0
- package/dist/audit-runs.js +68 -0
- package/dist/audit-runs.js.map +1 -0
- package/dist/backfill.d.ts +3 -7
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +115 -11
- package/dist/backfill.js.map +1 -1
- package/dist/background.d.ts +8 -1
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +202 -4
- package/dist/background.js.map +1 -1
- package/dist/broadcast-recorder.d.ts +27 -0
- package/dist/broadcast-recorder.d.ts.map +1 -0
- package/dist/broadcast-recorder.js +82 -0
- package/dist/broadcast-recorder.js.map +1 -0
- package/dist/channel-config-view.d.ts +28 -0
- package/dist/channel-config-view.d.ts.map +1 -0
- package/dist/channel-config-view.js +79 -0
- package/dist/channel-config-view.js.map +1 -0
- package/dist/channel-gate.d.ts +22 -0
- package/dist/channel-gate.d.ts.map +1 -0
- package/dist/channel-gate.js +35 -0
- package/dist/channel-gate.js.map +1 -0
- package/dist/channel-plugins.d.ts +39 -0
- package/dist/channel-plugins.d.ts.map +1 -0
- package/dist/channel-plugins.js +346 -0
- package/dist/channel-plugins.js.map +1 -0
- package/dist/channels.d.ts +62 -73
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +833 -126
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +53 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +696 -28
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts +24 -0
- package/dist/config-reference.d.ts.map +1 -0
- package/dist/config-reference.js +580 -0
- package/dist/config-reference.js.map +1 -0
- package/dist/configured-agent.d.ts +104 -0
- package/dist/configured-agent.d.ts.map +1 -0
- package/dist/configured-agent.js +578 -0
- package/dist/configured-agent.js.map +1 -0
- package/dist/consumer-contract.d.ts +36 -0
- package/dist/consumer-contract.d.ts.map +1 -0
- package/dist/consumer-contract.js +290 -0
- package/dist/consumer-contract.js.map +1 -0
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +252 -55
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +23 -7
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +91 -26
- package/dist/init.js.map +1 -1
- package/dist/install-skill.d.ts +1 -1
- package/dist/install-skill.js +2 -2
- package/dist/install-skill.js.map +1 -1
- package/dist/interaction-bridge.d.ts +72 -0
- package/dist/interaction-bridge.d.ts.map +1 -0
- package/dist/interaction-bridge.js +354 -0
- package/dist/interaction-bridge.js.map +1 -0
- package/dist/memory-command.d.ts +10 -0
- package/dist/memory-command.d.ts.map +1 -0
- package/dist/memory-command.js +649 -0
- package/dist/memory-command.js.map +1 -0
- package/dist/memory-recall-main.js +1 -1
- package/dist/memory-recall-main.js.map +1 -1
- package/dist/memory-recall.d.ts +41 -5
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +75 -5
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-rituals.d.ts +8 -11
- package/dist/memory-rituals.d.ts.map +1 -1
- package/dist/memory-rituals.js +26 -25
- package/dist/memory-rituals.js.map +1 -1
- package/dist/metrics.d.ts +13 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +105 -0
- package/dist/metrics.js.map +1 -0
- package/dist/notify-destinations.d.ts +23 -0
- package/dist/notify-destinations.d.ts.map +1 -0
- package/dist/notify-destinations.js +79 -0
- package/dist/notify-destinations.js.map +1 -0
- package/dist/posted-message-index.d.ts +61 -0
- package/dist/posted-message-index.d.ts.map +1 -0
- package/dist/posted-message-index.js +178 -0
- package/dist/posted-message-index.js.map +1 -0
- package/dist/proactive-notify.d.ts +32 -0
- package/dist/proactive-notify.d.ts.map +1 -0
- package/dist/proactive-notify.js +62 -0
- package/dist/proactive-notify.js.map +1 -0
- package/dist/recipes/base.d.ts +15 -0
- package/dist/recipes/base.d.ts.map +1 -0
- package/dist/recipes/base.js +51 -0
- package/dist/recipes/base.js.map +1 -0
- package/dist/recipes/catalog.d.ts +4 -0
- package/dist/recipes/catalog.d.ts.map +1 -0
- package/dist/recipes/catalog.js +509 -0
- package/dist/recipes/catalog.js.map +1 -0
- package/dist/recipes/index.d.ts +11 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +14 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/types.d.ts +70 -0
- package/dist/recipes/types.d.ts.map +1 -0
- package/dist/recipes/types.js +15 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/request-model-override.d.ts +67 -0
- package/dist/request-model-override.d.ts.map +1 -0
- package/dist/request-model-override.js +96 -0
- package/dist/request-model-override.js.map +1 -0
- package/dist/runs-health.d.ts +22 -0
- package/dist/runs-health.d.ts.map +1 -0
- package/dist/runs-health.js +144 -0
- package/dist/runs-health.js.map +1 -0
- package/dist/seen-conversations.d.ts +22 -0
- package/dist/seen-conversations.d.ts.map +1 -0
- package/dist/seen-conversations.js +120 -0
- package/dist/seen-conversations.js.map +1 -0
- package/dist/setup.d.ts +29 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +97 -0
- package/dist/setup.js.map +1 -0
- package/dist/trigger-overrides.d.ts +17 -0
- package/dist/trigger-overrides.d.ts.map +1 -0
- package/dist/trigger-overrides.js +30 -0
- package/dist/trigger-overrides.js.map +1 -0
- package/dist/tui-command.d.ts +56 -0
- package/dist/tui-command.d.ts.map +1 -0
- package/dist/tui-command.js +160 -0
- package/dist/tui-command.js.map +1 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +4 -0
- package/dist/ui.js.map +1 -1
- package/dist/web-command.d.ts +39 -0
- package/dist/web-command.d.ts.map +1 -0
- package/dist/web-command.js +151 -0
- package/dist/web-command.js.map +1 -0
- package/package.json +17 -17
- package/schema/mono-agent.config.schema.json +1686 -0
- package/skills/mono-agent-composer/SKILL.md +34 -9
- package/skills/mono-agent-composer/agents/openai.yaml +4 -0
- package/skills/mono-agent-composer/references/config-blueprint.md +52 -18
- package/skills/mono-agent-composer/references/discovery-questions.md +14 -16
- package/skills/mono-agent-composer/references/feature-coverage.md +19 -15
- package/skills/mono-agent-composer/references/package-map.md +17 -19
- package/skills/mono-agent-composer/references/playbooks.md +209 -0
- package/skills/mono-agent-composer/references/validation.md +9 -1
package/dist/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGzE,sEAAsE;AACtE,eAAO,MAAM,aAAa,gEAAiE,CAAC;AAC5F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAEjE;AAED,MAAM,WAAW,0BAA0B;IACzC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAC9C,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/C,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kEAAkE;IAClE,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAKD;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,yBAAyB,CAAC,CAqDpC"}
|
package/dist/init.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename, join, resolve } from "node:path";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { writeMonoAgentConfigJson } from "@mono-agent/config";
|
|
4
|
+
import { resolveRecipeInputs } from "./recipes/index.js";
|
|
5
|
+
import { monoAgentConfigWithSchema } from "./config-reference.js";
|
|
6
|
+
/** Channels `--with` can switch on, merged onto a recipe's config. */
|
|
7
|
+
export const WITH_CHANNELS = ["telegram", "slack", "webhook", "openaiApi", "cron"];
|
|
8
|
+
export function isWithChannel(value) {
|
|
9
|
+
return WITH_CHANNELS.includes(value);
|
|
10
|
+
}
|
|
4
11
|
const DEFAULT_MODEL = "claude:claude-sonnet-4-6";
|
|
5
12
|
const KNOWLEDGE_FILE_CANDIDATES = ["AGENTS.md", "CLAUDE.md", "README.md", "SOUL.md"];
|
|
6
13
|
/**
|
|
7
|
-
* Non-destructively scaffolds a config-first mono-agent folder: a
|
|
8
|
-
* `mono-agent.config.json` (
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* never overwritten.
|
|
14
|
+
* Non-destructively scaffolds a config-first mono-agent folder: a
|
|
15
|
+
* `mono-agent.config.json` (from the default smoke scaffold or a recipe), an
|
|
16
|
+
* `IDENTITY.md` seeded from any knowledge files already in the folder, the
|
|
17
|
+
* `.mono-agent/` working directories, and — for recipes — a `.env.example` and
|
|
18
|
+
* any recipe files. Existing files are never overwritten. With `dryRun`, nothing
|
|
19
|
+
* is written and `created` reports what would have been.
|
|
12
20
|
*/
|
|
13
21
|
export async function initMonoAgentFolder(options = {}) {
|
|
14
22
|
const dir = resolve(options.dir ?? process.cwd());
|
|
23
|
+
const dryRun = options.dryRun === true;
|
|
15
24
|
const created = [];
|
|
16
25
|
const skipped = [];
|
|
17
26
|
const knowledgeFiles = [];
|
|
@@ -20,33 +29,84 @@ export async function initMonoAgentFolder(options = {}) {
|
|
|
20
29
|
knowledgeFiles.push(candidate);
|
|
21
30
|
}
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
async function planFile(path, write) {
|
|
33
|
+
if (await pathExists(path)) {
|
|
34
|
+
skipped.push(path);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!dryRun) {
|
|
38
|
+
await write();
|
|
39
|
+
}
|
|
40
|
+
created.push(path);
|
|
30
41
|
}
|
|
42
|
+
const identityPath = join(dir, "IDENTITY.md");
|
|
43
|
+
await planFile(identityPath, () => writeFile(identityPath, identityTemplate(dir, knowledgeFiles), { flag: "wx" }));
|
|
31
44
|
for (const subdir of [join(dir, ".mono-agent", "artifacts"), join(dir, ".mono-agent", "workspace")]) {
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
await planFile(subdir, () => mkdir(subdir, { recursive: true }));
|
|
46
|
+
}
|
|
47
|
+
const skillsRootExists = await pathExists(join(dir, "skills"));
|
|
48
|
+
const configJson = await resolveConfigJson(dir, options, skillsRootExists);
|
|
49
|
+
const configPath = join(dir, "mono-agent.config.json");
|
|
50
|
+
await planFile(configPath, () => writeMonoAgentConfigJson({ path: configPath, patch: configJson }));
|
|
51
|
+
if (options.recipe !== undefined) {
|
|
52
|
+
const inputs = resolveRecipeInputs(options.recipe, recipeOverrides(options.recipe, options));
|
|
53
|
+
const envExample = options.recipe.envExample?.(inputs);
|
|
54
|
+
if (envExample !== undefined && envExample.trim().length > 0) {
|
|
55
|
+
const envExamplePath = join(dir, ".env.example");
|
|
56
|
+
await planFile(envExamplePath, () => writeFile(envExamplePath, envExample, { flag: "wx" }));
|
|
34
57
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
for (const file of options.recipe.files?.(inputs) ?? []) {
|
|
59
|
+
const filePath = join(dir, file.path);
|
|
60
|
+
await planFile(filePath, async () => {
|
|
61
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
62
|
+
await writeFile(filePath, file.contents, { flag: "wx" });
|
|
63
|
+
});
|
|
38
64
|
}
|
|
39
65
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
return { dir, configPath, identityPath, created, skipped, knowledgeFiles, dryRun };
|
|
67
|
+
}
|
|
68
|
+
function recipeOverrides(recipe, options) {
|
|
69
|
+
const overrides = { ...(options.recipeInputs ?? {}) };
|
|
70
|
+
for (const input of recipe.inputs) {
|
|
71
|
+
if (input.secret === true) {
|
|
72
|
+
delete overrides[input.id];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (options.model !== undefined) {
|
|
76
|
+
overrides.model = options.model;
|
|
77
|
+
}
|
|
78
|
+
return overrides;
|
|
79
|
+
}
|
|
80
|
+
async function resolveConfigJson(dir, options, skillsRootExists) {
|
|
81
|
+
if (options.recipe !== undefined) {
|
|
82
|
+
const inputs = resolveRecipeInputs(options.recipe, recipeOverrides(options.recipe, options));
|
|
83
|
+
return monoAgentConfigWithSchema(withChannels(withRuntimeOverrides(options.recipe.config(inputs), options), options.withChannels ?? []));
|
|
84
|
+
}
|
|
85
|
+
return monoAgentConfigWithSchema(withChannels(configTemplate(dir, options, skillsRootExists), options.withChannels ?? []));
|
|
86
|
+
}
|
|
87
|
+
function withRuntimeOverrides(config, options) {
|
|
88
|
+
const fallbackModels = (options.fallbackModels ?? []).filter((entry) => entry.trim().length > 0);
|
|
89
|
+
if (fallbackModels.length === 0) {
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...config,
|
|
94
|
+
runtime: {
|
|
95
|
+
...(config.runtime ?? {}),
|
|
96
|
+
fallbackModels,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Switch on additional channels by merging `{ <channel>: { enabled: true } }`. */
|
|
101
|
+
function withChannels(config, channels) {
|
|
102
|
+
if (channels.length === 0) {
|
|
103
|
+
return config;
|
|
43
104
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
created.push(configPath);
|
|
105
|
+
const extra = {};
|
|
106
|
+
for (const channel of channels) {
|
|
107
|
+
extra[channel] = { enabled: true };
|
|
48
108
|
}
|
|
49
|
-
return {
|
|
109
|
+
return { ...config, ...extra };
|
|
50
110
|
}
|
|
51
111
|
function configTemplate(dir, options, skillsRootExists) {
|
|
52
112
|
const fallbackModels = (options.fallbackModels ?? []).filter((entry) => entry.trim().length > 0);
|
|
@@ -76,6 +136,11 @@ function configTemplate(dir, options, skillsRootExists) {
|
|
|
76
136
|
},
|
|
77
137
|
artifacts: {
|
|
78
138
|
dir: "./.mono-agent/artifacts",
|
|
139
|
+
retention: {
|
|
140
|
+
maxAgeDays: 365,
|
|
141
|
+
maxCount: 50000,
|
|
142
|
+
dryRun: false,
|
|
143
|
+
},
|
|
79
144
|
},
|
|
80
145
|
traceability: {
|
|
81
146
|
registryDir: "./.mono-agent/trace-sources",
|
package/dist/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
package/dist/install-skill.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface InstallSkillResult {
|
|
|
14
14
|
export declare const COMPOSER_SKILL_NAME = "mono-agent-composer";
|
|
15
15
|
/**
|
|
16
16
|
* Copies the bundled mono-agent-composer skill into the harness skill folders
|
|
17
|
-
* (~/.claude/skills and/or ~/.
|
|
17
|
+
* (~/.claude/skills and/or ~/.agents/skills) so Claude Code and Codex can use
|
|
18
18
|
* it to compose new agents.
|
|
19
19
|
*/
|
|
20
20
|
export declare function installComposerSkill(options: InstallSkillOptions): Promise<InstallSkillResult>;
|
package/dist/install-skill.js
CHANGED
|
@@ -8,11 +8,11 @@ export const COMPOSER_SKILL_NAME = "mono-agent-composer";
|
|
|
8
8
|
const BUNDLED_SKILL_DIR = fileURLToPath(new URL(`../skills/${COMPOSER_SKILL_NAME}`, import.meta.url));
|
|
9
9
|
const TARGET_HARNESS_DIRS = {
|
|
10
10
|
claude: ".claude",
|
|
11
|
-
codex: ".
|
|
11
|
+
codex: ".agents",
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* Copies the bundled mono-agent-composer skill into the harness skill folders
|
|
15
|
-
* (~/.claude/skills and/or ~/.
|
|
15
|
+
* (~/.claude/skills and/or ~/.agents/skills) so Claude Code and Codex can use
|
|
16
16
|
* it to compose new agents.
|
|
17
17
|
*/
|
|
18
18
|
export async function installComposerSkill(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-skill.js","sourceRoot":"","sources":["../src/install-skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkBzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAEzD,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,aAAa,mBAAmB,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtG,MAAM,mBAAmB,GAAkE;IACzF,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"install-skill.js","sourceRoot":"","sources":["../src/install-skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkBzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAEzD,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,aAAa,mBAAmB,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtG,MAAM,mBAAmB,GAAkE;IACzF,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,SAAS;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA4B;IACrE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iBAAiB,CAAC;IACzD,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAEnC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAE,CAAC,QAAQ,EAAE,OAAO,CAAW,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,MAAM,CAAW,CAAC;IACzG,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAErH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;YACvC,IAAI,MAAM,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,eAAe,WAAW,oDAAoD,CAAC,CAAC;YAClG,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,SAAiB;IAChD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,wCAAwC,SAAS,6BAA6B,CAAC,CAAC;IAClG,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction bridge: the app-owned loopback HTTP surface that lets tool child
|
|
3
|
+
* processes (adapter-send-tools, project MCP servers) interact with the human
|
|
4
|
+
* behind a channel conversation while a tool call is in flight.
|
|
5
|
+
*
|
|
6
|
+
* Two capabilities share it:
|
|
7
|
+
* - Blocking `ask_user`: POST /v1/asks registers a pending ask (question posted
|
|
8
|
+
* through the channel's sink); the tool long-polls GET /v1/asks/:id until the
|
|
9
|
+
* channel intercepts the user's next message and resolves it.
|
|
10
|
+
* - Tool progress: POST /v1/progress fans out to the channel sink's postStatus
|
|
11
|
+
* (e.g. a Telegram status message edited in place).
|
|
12
|
+
*
|
|
13
|
+
* The registry is in-memory by design: on an app restart pending asks vanish and
|
|
14
|
+
* the user's later reply simply arrives as a normal next turn (multi-turn
|
|
15
|
+
* degradation), which the ask_user tool description tells the model to expect.
|
|
16
|
+
*/
|
|
17
|
+
export interface ChannelInteractionSink {
|
|
18
|
+
/** Post a free-text question to the conversation's chat. */
|
|
19
|
+
postQuestion(conversationId: string, text: string): Promise<void>;
|
|
20
|
+
/** Post (or edit in place, per `key`) a short tool-progress status line. */
|
|
21
|
+
postStatus(conversationId: string, text: string, options: {
|
|
22
|
+
readonly key: string;
|
|
23
|
+
readonly state: "working" | "done" | "failed";
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface InteractionBridgeOptions {
|
|
27
|
+
readonly host?: string;
|
|
28
|
+
/** TCP port; 0 picks an ephemeral port. Default {@link DEFAULT_INTERACTION_BRIDGE_PORT}. */
|
|
29
|
+
readonly port?: number;
|
|
30
|
+
/** Default + upper bound for a single ask's wait (ms). Default 10 minutes. */
|
|
31
|
+
readonly askTimeoutMs?: number;
|
|
32
|
+
readonly logger?: {
|
|
33
|
+
warn?: (message: string, metadata?: Record<string, unknown>) => void;
|
|
34
|
+
debug?: (message: string, metadata?: Record<string, unknown>) => void;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface InteractionBridgeHandle {
|
|
38
|
+
readonly url: string;
|
|
39
|
+
readonly token: string;
|
|
40
|
+
registerSink(channelId: string, sink: ChannelInteractionSink): void;
|
|
41
|
+
/** Resolve the conversation's pending ask with the user's reply; true when consumed. */
|
|
42
|
+
tryResolveAsk(conversationId: string, answer: string): boolean;
|
|
43
|
+
/** Fail every pending ask on the conversation (user cancelled the run). */
|
|
44
|
+
cancelAsks(conversationId: string): void;
|
|
45
|
+
/** Environment consumed by tool child processes (ask_user, project MCP servers). */
|
|
46
|
+
env(): Record<string, string>;
|
|
47
|
+
stop(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** Default port 0 = ephemeral: consumers get the URL via env, so a fixed port only invites collisions. */
|
|
50
|
+
export declare const DEFAULT_INTERACTION_BRIDGE_PORT = 0;
|
|
51
|
+
export declare const DEFAULT_ASK_USER_TIMEOUT_MS = 600000;
|
|
52
|
+
/** Resolved `interaction` config block (JSON `interaction` key + env overrides). */
|
|
53
|
+
export interface InteractionSettings {
|
|
54
|
+
/** True when the operator explicitly configured the block (JSON or env). */
|
|
55
|
+
readonly configured: boolean;
|
|
56
|
+
readonly host: string;
|
|
57
|
+
readonly port: number;
|
|
58
|
+
readonly askTimeoutMs: number;
|
|
59
|
+
readonly progressEnabled: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load the app-level `interaction` block from `mono-agent.config.json` with env
|
|
63
|
+
* overrides (`MONO_AGENT_INTERACTION_BRIDGE_HOST/PORT`,
|
|
64
|
+
* `MONO_AGENT_ASK_USER_TIMEOUT_MS`, `MONO_AGENT_PROGRESS_ENABLED`). Tolerant of
|
|
65
|
+
* a missing file/block: returns unconfigured defaults.
|
|
66
|
+
*/
|
|
67
|
+
export declare function loadInteractionSettings(input: {
|
|
68
|
+
readonly env: Record<string, string | undefined>;
|
|
69
|
+
readonly configPath: string;
|
|
70
|
+
}): Promise<InteractionSettings>;
|
|
71
|
+
export declare function startInteractionBridge(options?: InteractionBridgeOptions): Promise<InteractionBridgeHandle>;
|
|
72
|
+
//# sourceMappingURL=interaction-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-bridge.d.ts","sourceRoot":"","sources":["../src/interaction-bridge.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,4EAA4E;IAC5E,UAAU,CACR,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;KAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QACrE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KACvE,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACpE,wFAAwF;IACxF,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/D,2EAA2E;IAC3E,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,oFAAoF;IACpF,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,0GAA0G;AAC1G,eAAO,MAAM,+BAA+B,IAAI,CAAC;AACjD,eAAO,MAAM,2BAA2B,SAAU,CAAC;AAEnD,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8B/B;AA+CD,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CAwPlC"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
/** Default port 0 = ephemeral: consumers get the URL via env, so a fixed port only invites collisions. */
|
|
5
|
+
export const DEFAULT_INTERACTION_BRIDGE_PORT = 0;
|
|
6
|
+
export const DEFAULT_ASK_USER_TIMEOUT_MS = 600_000;
|
|
7
|
+
/**
|
|
8
|
+
* Load the app-level `interaction` block from `mono-agent.config.json` with env
|
|
9
|
+
* overrides (`MONO_AGENT_INTERACTION_BRIDGE_HOST/PORT`,
|
|
10
|
+
* `MONO_AGENT_ASK_USER_TIMEOUT_MS`, `MONO_AGENT_PROGRESS_ENABLED`). Tolerant of
|
|
11
|
+
* a missing file/block: returns unconfigured defaults.
|
|
12
|
+
*/
|
|
13
|
+
export async function loadInteractionSettings(input) {
|
|
14
|
+
let block = {};
|
|
15
|
+
let present = false;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(await readFile(input.configPath, "utf8"));
|
|
18
|
+
const interaction = parsed?.interaction;
|
|
19
|
+
if (typeof interaction === "object" && interaction !== null) {
|
|
20
|
+
block = interaction;
|
|
21
|
+
present = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Missing or invalid config file: the core loader reports that; here we just
|
|
26
|
+
// fall back to defaults.
|
|
27
|
+
}
|
|
28
|
+
const bridge = (block.bridge ?? {});
|
|
29
|
+
const askUser = (block.askUser ?? {});
|
|
30
|
+
const progress = (block.progress ?? {});
|
|
31
|
+
const envHost = trimmed(input.env.MONO_AGENT_INTERACTION_BRIDGE_HOST);
|
|
32
|
+
const envPort = integerOf(input.env.MONO_AGENT_INTERACTION_BRIDGE_PORT);
|
|
33
|
+
const envTimeout = integerOf(input.env.MONO_AGENT_ASK_USER_TIMEOUT_MS);
|
|
34
|
+
const envProgress = trimmed(input.env.MONO_AGENT_PROGRESS_ENABLED);
|
|
35
|
+
const configured = present || envHost !== undefined || envPort !== undefined || envTimeout !== undefined || envProgress !== undefined;
|
|
36
|
+
return {
|
|
37
|
+
configured,
|
|
38
|
+
host: envHost ?? (typeof bridge.host === "string" ? bridge.host : "127.0.0.1"),
|
|
39
|
+
port: envPort ?? integerValue(bridge.port) ?? DEFAULT_INTERACTION_BRIDGE_PORT,
|
|
40
|
+
askTimeoutMs: envTimeout ?? integerValue(askUser.timeoutMs) ?? DEFAULT_ASK_USER_TIMEOUT_MS,
|
|
41
|
+
progressEnabled: envProgress !== undefined ? envProgress !== "false" : progress.enabled !== false,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function trimmed(value) {
|
|
45
|
+
const normalized = value?.trim();
|
|
46
|
+
return normalized === undefined || normalized.length === 0 ? undefined : normalized;
|
|
47
|
+
}
|
|
48
|
+
function integerOf(value) {
|
|
49
|
+
const normalized = trimmed(value);
|
|
50
|
+
if (normalized === undefined || !/^\d+$/u.test(normalized)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return Number.parseInt(normalized, 10);
|
|
54
|
+
}
|
|
55
|
+
function integerValue(value) {
|
|
56
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Per-request long-poll wait cap, keeping every HTTP request comfortably short-lived. */
|
|
59
|
+
const MAX_LONG_POLL_WAIT_MS = 25_000;
|
|
60
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
61
|
+
/** Strip the daily-rollover `#bucket` suffix so registry keys match the channel's base id. */
|
|
62
|
+
function normalizeConversationId(conversationId) {
|
|
63
|
+
return conversationId.split("#", 1)[0] ?? conversationId;
|
|
64
|
+
}
|
|
65
|
+
function channelIdOf(conversationId) {
|
|
66
|
+
return conversationId.split(":", 1)[0] ?? conversationId;
|
|
67
|
+
}
|
|
68
|
+
export async function startInteractionBridge(options = {}) {
|
|
69
|
+
const host = options.host ?? "127.0.0.1";
|
|
70
|
+
const askTimeoutMs = options.askTimeoutMs ?? DEFAULT_ASK_USER_TIMEOUT_MS;
|
|
71
|
+
const token = randomBytes(24).toString("base64url");
|
|
72
|
+
const sinks = new Map();
|
|
73
|
+
// One pending ask per conversation; a second concurrent ask is a 409 by design
|
|
74
|
+
// (the model must consolidate its questions instead of stacking them).
|
|
75
|
+
const asksByConversation = new Map();
|
|
76
|
+
const asksById = new Map();
|
|
77
|
+
let askCounter = 0;
|
|
78
|
+
function settleAsk(ask, status, answer) {
|
|
79
|
+
if (ask.status !== "pending") {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
ask.status = status;
|
|
83
|
+
if (answer !== undefined) {
|
|
84
|
+
ask.answer = answer;
|
|
85
|
+
}
|
|
86
|
+
clearTimeout(ask.expiryTimer);
|
|
87
|
+
asksByConversation.delete(ask.conversationId);
|
|
88
|
+
const snapshot = snapshotOf(ask);
|
|
89
|
+
for (const waiter of ask.waiters) {
|
|
90
|
+
waiter(snapshot);
|
|
91
|
+
}
|
|
92
|
+
ask.waiters.clear();
|
|
93
|
+
}
|
|
94
|
+
function snapshotOf(ask) {
|
|
95
|
+
return ask.status === "answered" && ask.answer !== undefined
|
|
96
|
+
? { status: ask.status, answer: ask.answer }
|
|
97
|
+
: { status: ask.status };
|
|
98
|
+
}
|
|
99
|
+
function registerAsk(conversationId, question, timeoutMs) {
|
|
100
|
+
askCounter += 1;
|
|
101
|
+
const askId = `ask-${String(askCounter)}-${randomBytes(6).toString("base64url")}`;
|
|
102
|
+
const ask = {
|
|
103
|
+
askId,
|
|
104
|
+
conversationId,
|
|
105
|
+
status: "pending",
|
|
106
|
+
waiters: new Set(),
|
|
107
|
+
expiryTimer: setTimeout(() => settleAsk(ask, "expired"), timeoutMs),
|
|
108
|
+
};
|
|
109
|
+
ask.expiryTimer.unref?.();
|
|
110
|
+
asksByConversation.set(conversationId, ask);
|
|
111
|
+
asksById.set(askId, ask);
|
|
112
|
+
return ask;
|
|
113
|
+
}
|
|
114
|
+
async function handleCreateAsk(request, response) {
|
|
115
|
+
const body = await readJsonBody(request);
|
|
116
|
+
const conversationIdRaw = stringField(body, "conversationId");
|
|
117
|
+
const question = stringField(body, "question");
|
|
118
|
+
if (conversationIdRaw === undefined || question === undefined) {
|
|
119
|
+
sendJson(response, 400, { error: "conversationId and question are required." });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const conversationId = normalizeConversationId(conversationIdRaw);
|
|
123
|
+
const sink = sinks.get(channelIdOf(conversationId));
|
|
124
|
+
if (sink === undefined) {
|
|
125
|
+
sendJson(response, 501, {
|
|
126
|
+
error: `no interactive channel is registered for "${channelIdOf(conversationId)}" — ask in your final reply instead.`,
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (asksByConversation.has(conversationId)) {
|
|
131
|
+
sendJson(response, 409, { error: "a question is already pending on this conversation; wait for its answer." });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const requested = numberField(body, "timeoutMs");
|
|
135
|
+
// The config value is both the default and the ceiling: tools may wait less,
|
|
136
|
+
// never more, than the operator allowed.
|
|
137
|
+
const timeoutMs = Math.min(requested ?? askTimeoutMs, askTimeoutMs);
|
|
138
|
+
const ask = registerAsk(conversationId, question, timeoutMs);
|
|
139
|
+
try {
|
|
140
|
+
await sink.postQuestion(conversationId, question);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
settleAsk(ask, "cancelled");
|
|
144
|
+
asksById.delete(ask.askId);
|
|
145
|
+
options.logger?.warn?.("interaction bridge: posting the ask question failed.", {
|
|
146
|
+
conversationId,
|
|
147
|
+
error: error instanceof Error ? error.message : String(error),
|
|
148
|
+
});
|
|
149
|
+
sendJson(response, 502, { error: "posting the question to the channel failed." });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
sendJson(response, 201, { askId: ask.askId, timeoutMs });
|
|
153
|
+
}
|
|
154
|
+
function handleAwaitAsk(request, response, askId, url) {
|
|
155
|
+
const ask = asksById.get(askId);
|
|
156
|
+
if (ask === undefined) {
|
|
157
|
+
sendJson(response, 404, { error: "unknown askId." });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (ask.status !== "pending") {
|
|
161
|
+
// Terminal snapshots are single-consumer: the asking tool reads it once.
|
|
162
|
+
asksById.delete(askId);
|
|
163
|
+
sendJson(response, 200, snapshotOf(ask));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const waitMs = Math.min(Math.max(Number(url.searchParams.get("waitMs")) || 0, 0), MAX_LONG_POLL_WAIT_MS);
|
|
167
|
+
if (waitMs === 0) {
|
|
168
|
+
sendJson(response, 200, snapshotOf(ask));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let settled = false;
|
|
172
|
+
const respond = (snapshot) => {
|
|
173
|
+
if (settled) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
settled = true;
|
|
177
|
+
clearTimeout(pollTimer);
|
|
178
|
+
ask.waiters.delete(respond);
|
|
179
|
+
if (snapshot.status !== "pending") {
|
|
180
|
+
asksById.delete(askId);
|
|
181
|
+
}
|
|
182
|
+
sendJson(response, 200, snapshot);
|
|
183
|
+
};
|
|
184
|
+
const pollTimer = setTimeout(() => respond({ status: "pending" }), waitMs);
|
|
185
|
+
pollTimer.unref?.();
|
|
186
|
+
ask.waiters.add(respond);
|
|
187
|
+
request.on("close", () => {
|
|
188
|
+
clearTimeout(pollTimer);
|
|
189
|
+
ask.waiters.delete(respond);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async function handleProgress(request, response) {
|
|
193
|
+
const body = await readJsonBody(request);
|
|
194
|
+
const conversationIdRaw = stringField(body, "conversationId");
|
|
195
|
+
const key = stringField(body, "key");
|
|
196
|
+
const message = stringField(body, "message");
|
|
197
|
+
if (conversationIdRaw === undefined || key === undefined || message === undefined) {
|
|
198
|
+
sendJson(response, 400, { error: "conversationId, key, and message are required." });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const state = stringField(body, "state");
|
|
202
|
+
const conversationId = normalizeConversationId(conversationIdRaw);
|
|
203
|
+
const sink = sinks.get(channelIdOf(conversationId));
|
|
204
|
+
if (sink === undefined) {
|
|
205
|
+
sendJson(response, 501, { error: "no interactive channel is registered for this conversation." });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Accepted before delivery: progress is best-effort by contract and a slow
|
|
209
|
+
// channel edit must not stall the reporting tool.
|
|
210
|
+
sendJson(response, 202, { ok: true });
|
|
211
|
+
void sink
|
|
212
|
+
.postStatus(conversationId, message, {
|
|
213
|
+
key,
|
|
214
|
+
state: state === "done" || state === "failed" ? state : "working",
|
|
215
|
+
})
|
|
216
|
+
.catch((error) => {
|
|
217
|
+
options.logger?.debug?.("interaction bridge: progress post failed (best-effort).", {
|
|
218
|
+
conversationId,
|
|
219
|
+
error: error instanceof Error ? error.message : String(error),
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const server = createServer((request, response) => {
|
|
224
|
+
void routeRequest(request, response).catch((error) => {
|
|
225
|
+
options.logger?.warn?.("interaction bridge: request handling failed.", {
|
|
226
|
+
error: error instanceof Error ? error.message : String(error),
|
|
227
|
+
});
|
|
228
|
+
if (!response.headersSent) {
|
|
229
|
+
sendJson(response, 500, { error: "internal error." });
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
response.end();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
async function routeRequest(request, response) {
|
|
237
|
+
if (request.headers.authorization !== `Bearer ${token}`) {
|
|
238
|
+
sendJson(response, 401, { error: "missing or invalid bearer token." });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const url = new URL(request.url ?? "/", "http://bridge.local");
|
|
242
|
+
if (request.method === "POST" && url.pathname === "/v1/asks") {
|
|
243
|
+
await handleCreateAsk(request, response);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const askMatch = /^\/v1\/asks\/([^/]+)$/u.exec(url.pathname);
|
|
247
|
+
if (askMatch !== null && request.method === "GET") {
|
|
248
|
+
handleAwaitAsk(request, response, askMatch[1], url);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (askMatch !== null && request.method === "DELETE") {
|
|
252
|
+
const ask = asksById.get(askMatch[1]);
|
|
253
|
+
if (ask !== undefined) {
|
|
254
|
+
settleAsk(ask, "cancelled");
|
|
255
|
+
asksById.delete(ask.askId);
|
|
256
|
+
}
|
|
257
|
+
response.statusCode = 204;
|
|
258
|
+
response.end();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (request.method === "POST" && url.pathname === "/v1/progress") {
|
|
262
|
+
await handleProgress(request, response);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
sendJson(response, 404, { error: "unknown route." });
|
|
266
|
+
}
|
|
267
|
+
await listenOn(server, host, options.port ?? DEFAULT_INTERACTION_BRIDGE_PORT);
|
|
268
|
+
const address = server.address();
|
|
269
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
270
|
+
const url = `http://${host}:${String(port)}`;
|
|
271
|
+
return {
|
|
272
|
+
url,
|
|
273
|
+
token,
|
|
274
|
+
registerSink(channelId, sink) {
|
|
275
|
+
sinks.set(channelId, sink);
|
|
276
|
+
},
|
|
277
|
+
tryResolveAsk(conversationId, answer) {
|
|
278
|
+
const ask = asksByConversation.get(normalizeConversationId(conversationId));
|
|
279
|
+
if (ask === undefined) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
settleAsk(ask, "answered", answer);
|
|
283
|
+
return true;
|
|
284
|
+
},
|
|
285
|
+
cancelAsks(conversationId) {
|
|
286
|
+
const ask = asksByConversation.get(normalizeConversationId(conversationId));
|
|
287
|
+
if (ask !== undefined) {
|
|
288
|
+
settleAsk(ask, "cancelled");
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
env() {
|
|
292
|
+
return {
|
|
293
|
+
MONO_AGENT_INTERACTION_BRIDGE_URL: url,
|
|
294
|
+
MONO_AGENT_INTERACTION_BRIDGE_TOKEN: token,
|
|
295
|
+
MONO_AGENT_ASK_USER_TIMEOUT_MS: String(askTimeoutMs),
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
async stop() {
|
|
299
|
+
for (const ask of asksById.values()) {
|
|
300
|
+
settleAsk(ask, "cancelled");
|
|
301
|
+
}
|
|
302
|
+
asksById.clear();
|
|
303
|
+
await new Promise((resolve, reject) => {
|
|
304
|
+
server.close((error) => (error === undefined || error === null ? resolve() : reject(error)));
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async function listenOn(server, host, port) {
|
|
310
|
+
await new Promise((resolve, reject) => {
|
|
311
|
+
server.once("error", reject);
|
|
312
|
+
server.listen(port, host, () => {
|
|
313
|
+
server.removeListener("error", reject);
|
|
314
|
+
resolve();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
async function readJsonBody(request) {
|
|
319
|
+
const chunks = [];
|
|
320
|
+
let total = 0;
|
|
321
|
+
for await (const chunk of request) {
|
|
322
|
+
const buffer = chunk;
|
|
323
|
+
total += buffer.byteLength;
|
|
324
|
+
if (total > MAX_BODY_BYTES) {
|
|
325
|
+
throw new Error("request body too large.");
|
|
326
|
+
}
|
|
327
|
+
chunks.push(buffer);
|
|
328
|
+
}
|
|
329
|
+
if (total === 0) {
|
|
330
|
+
return {};
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
334
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return {};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function stringField(body, key) {
|
|
341
|
+
const value = body[key];
|
|
342
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
343
|
+
}
|
|
344
|
+
function numberField(body, key) {
|
|
345
|
+
const value = body[key];
|
|
346
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
347
|
+
}
|
|
348
|
+
function sendJson(response, statusCode, body) {
|
|
349
|
+
const payload = JSON.stringify(body);
|
|
350
|
+
response.statusCode = statusCode;
|
|
351
|
+
response.setHeader("content-type", "application/json");
|
|
352
|
+
response.end(payload);
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=interaction-bridge.js.map
|