@ouro.bot/cli 0.1.0-alpha.7 → 0.1.0-alpha.70
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +387 -0
- package/dist/heart/active-work.js +178 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/config.js +68 -23
- package/dist/heart/core.js +282 -92
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +409 -0
- package/dist/heart/daemon/daemon-cli.js +1395 -248
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +216 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +64 -5
- package/dist/heart/daemon/specialist-tools.js +213 -58
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +379 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +126 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/github-copilot.js +149 -0
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +358 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +279 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +195 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +642 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +93 -52
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +893 -45
- package/dist/senses/cli-layout.js +87 -0
- package/dist/senses/cli.js +348 -144
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +148 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +333 -84
- package/dist/senses/pipeline.js +278 -0
- package/dist/senses/teams.js +573 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.McpManager = void 0;
|
|
4
|
+
exports.getSharedMcpManager = getSharedMcpManager;
|
|
5
|
+
exports.shutdownSharedMcpManager = shutdownSharedMcpManager;
|
|
6
|
+
exports.resetSharedMcpManager = resetSharedMcpManager;
|
|
7
|
+
const mcp_client_1 = require("./mcp-client");
|
|
8
|
+
const identity_1 = require("../heart/identity");
|
|
9
|
+
const runtime_1 = require("../nerves/runtime");
|
|
10
|
+
const MAX_RESTART_RETRIES = 5;
|
|
11
|
+
const RESTART_DELAY_MS = 1000;
|
|
12
|
+
class McpManager {
|
|
13
|
+
servers = new Map();
|
|
14
|
+
shuttingDown = false;
|
|
15
|
+
async start(servers) {
|
|
16
|
+
(0, runtime_1.emitNervesEvent)({
|
|
17
|
+
event: "mcp.manager_start",
|
|
18
|
+
component: "repertoire",
|
|
19
|
+
message: "starting MCP manager",
|
|
20
|
+
meta: { serverCount: Object.keys(servers).length },
|
|
21
|
+
});
|
|
22
|
+
const entries = Object.entries(servers);
|
|
23
|
+
for (const [name, config] of entries) {
|
|
24
|
+
await this.connectServer(name, config);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
listAllTools() {
|
|
28
|
+
const result = [];
|
|
29
|
+
for (const [name, entry] of this.servers) {
|
|
30
|
+
result.push({ server: name, tools: entry.cachedTools });
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
async callTool(server, tool, args) {
|
|
35
|
+
const entry = this.servers.get(server);
|
|
36
|
+
if (!entry) {
|
|
37
|
+
throw new Error(`Unknown server: ${server}`);
|
|
38
|
+
}
|
|
39
|
+
if (!entry.client.isConnected()) {
|
|
40
|
+
throw new Error(`Server "${server}" is disconnected`);
|
|
41
|
+
}
|
|
42
|
+
return entry.client.callTool(tool, args);
|
|
43
|
+
}
|
|
44
|
+
shutdown() {
|
|
45
|
+
this.shuttingDown = true;
|
|
46
|
+
(0, runtime_1.emitNervesEvent)({
|
|
47
|
+
event: "mcp.manager_stop",
|
|
48
|
+
component: "repertoire",
|
|
49
|
+
message: "shutting down MCP manager",
|
|
50
|
+
meta: { serverCount: this.servers.size },
|
|
51
|
+
});
|
|
52
|
+
for (const [, entry] of this.servers) {
|
|
53
|
+
entry.client.shutdown();
|
|
54
|
+
}
|
|
55
|
+
this.servers.clear();
|
|
56
|
+
}
|
|
57
|
+
async connectServer(name, config) {
|
|
58
|
+
const client = new mcp_client_1.McpClient(config);
|
|
59
|
+
const entry = {
|
|
60
|
+
name,
|
|
61
|
+
config,
|
|
62
|
+
client,
|
|
63
|
+
cachedTools: [],
|
|
64
|
+
consecutiveFailures: 0,
|
|
65
|
+
};
|
|
66
|
+
this.servers.set(name, entry);
|
|
67
|
+
client.onClose(() => {
|
|
68
|
+
if (this.shuttingDown)
|
|
69
|
+
return;
|
|
70
|
+
this.handleServerCrash(name);
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
await client.connect();
|
|
74
|
+
const tools = await client.listTools();
|
|
75
|
+
entry.cachedTools = tools;
|
|
76
|
+
entry.consecutiveFailures = 0;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
(0, runtime_1.emitNervesEvent)({
|
|
80
|
+
level: "error",
|
|
81
|
+
event: "mcp.connect_error",
|
|
82
|
+
component: "repertoire",
|
|
83
|
+
message: `failed to connect MCP server: ${name}`,
|
|
84
|
+
meta: {
|
|
85
|
+
server: name,
|
|
86
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
handleServerCrash(name) {
|
|
92
|
+
const entry = this.servers.get(name);
|
|
93
|
+
/* v8 ignore next -- defensive: entry removed between close event and handler @preserve */
|
|
94
|
+
if (!entry)
|
|
95
|
+
return;
|
|
96
|
+
entry.consecutiveFailures++;
|
|
97
|
+
if (entry.consecutiveFailures > MAX_RESTART_RETRIES) {
|
|
98
|
+
(0, runtime_1.emitNervesEvent)({
|
|
99
|
+
level: "error",
|
|
100
|
+
event: "mcp.connect_error",
|
|
101
|
+
component: "repertoire",
|
|
102
|
+
message: `MCP server "${name}" exceeded max restart retries (${MAX_RESTART_RETRIES})`,
|
|
103
|
+
meta: { server: name, failures: entry.consecutiveFailures },
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
(0, runtime_1.emitNervesEvent)({
|
|
108
|
+
level: "warn",
|
|
109
|
+
event: "mcp.server_restart",
|
|
110
|
+
component: "repertoire",
|
|
111
|
+
message: `restarting crashed MCP server: ${name}`,
|
|
112
|
+
meta: { server: name, attempt: entry.consecutiveFailures },
|
|
113
|
+
});
|
|
114
|
+
/* v8 ignore start -- timer callback: covered by mcp-manager.test.ts via fake timers but v8 can't trace @preserve */
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
if (this.shuttingDown)
|
|
117
|
+
return;
|
|
118
|
+
this.restartServer(name).catch(() => {
|
|
119
|
+
// Error handling is inside restartServer
|
|
120
|
+
});
|
|
121
|
+
}, RESTART_DELAY_MS);
|
|
122
|
+
/* v8 ignore stop */
|
|
123
|
+
}
|
|
124
|
+
/* v8 ignore start -- called from timer callback: covered by mcp-manager.test.ts via fake timers but v8 can't trace @preserve */
|
|
125
|
+
async restartServer(name) {
|
|
126
|
+
const entry = this.servers.get(name);
|
|
127
|
+
if (!entry)
|
|
128
|
+
return;
|
|
129
|
+
// Remove old entry and reconnect
|
|
130
|
+
this.servers.delete(name);
|
|
131
|
+
await this.connectServer(name, entry.config);
|
|
132
|
+
// Preserve failure count
|
|
133
|
+
const newEntry = this.servers.get(name);
|
|
134
|
+
if (newEntry) {
|
|
135
|
+
newEntry.consecutiveFailures = entry.consecutiveFailures;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.McpManager = McpManager;
|
|
140
|
+
let _sharedManager = null;
|
|
141
|
+
let _sharedManagerPromise = null;
|
|
142
|
+
/**
|
|
143
|
+
* Get or create a shared McpManager instance from the agent's config.
|
|
144
|
+
* Returns null if no mcpServers are configured.
|
|
145
|
+
* Safe to call from multiple senses — will only create one instance.
|
|
146
|
+
*/
|
|
147
|
+
async function getSharedMcpManager() {
|
|
148
|
+
if (_sharedManager)
|
|
149
|
+
return _sharedManager;
|
|
150
|
+
/* v8 ignore next -- race guard: deduplicates concurrent initialization calls @preserve */
|
|
151
|
+
if (_sharedManagerPromise)
|
|
152
|
+
return _sharedManagerPromise;
|
|
153
|
+
_sharedManagerPromise = (async () => {
|
|
154
|
+
try {
|
|
155
|
+
const config = (0, identity_1.loadAgentConfig)();
|
|
156
|
+
const servers = config.mcpServers;
|
|
157
|
+
if (!servers || Object.keys(servers).length === 0)
|
|
158
|
+
return null;
|
|
159
|
+
const manager = new McpManager();
|
|
160
|
+
await manager.start(servers);
|
|
161
|
+
_sharedManager = manager;
|
|
162
|
+
return manager;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
(0, runtime_1.emitNervesEvent)({
|
|
166
|
+
level: "error",
|
|
167
|
+
event: "mcp.manager_start",
|
|
168
|
+
component: "repertoire",
|
|
169
|
+
message: "failed to initialize shared MCP manager",
|
|
170
|
+
/* v8 ignore next -- both branches tested: Error in wiring test, non-Error is defensive @preserve */
|
|
171
|
+
meta: { reason: error instanceof Error ? error.message : String(error) },
|
|
172
|
+
});
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
_sharedManagerPromise = null;
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
179
|
+
return _sharedManagerPromise;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Shut down the shared MCP manager and clear the singleton.
|
|
183
|
+
* Called during daemon/agent shutdown.
|
|
184
|
+
*/
|
|
185
|
+
function shutdownSharedMcpManager() {
|
|
186
|
+
if (_sharedManager) {
|
|
187
|
+
_sharedManager.shutdown();
|
|
188
|
+
_sharedManager = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Reset for testing only */
|
|
192
|
+
function resetSharedMcpManager() {
|
|
193
|
+
_sharedManager = null;
|
|
194
|
+
_sharedManagerPromise = null;
|
|
195
|
+
}
|
|
@@ -50,10 +50,6 @@ function getSkillsDir() {
|
|
|
50
50
|
function getProtocolMirrorDir() {
|
|
51
51
|
return path.join(getSkillsDir(), "protocols");
|
|
52
52
|
}
|
|
53
|
-
// Canonical protocol source lives in {repoRoot}/subagents/.
|
|
54
|
-
function getCanonicalProtocolsDir() {
|
|
55
|
-
return path.join((0, identity_1.getRepoRoot)(), "subagents");
|
|
56
|
-
}
|
|
57
53
|
function listMarkdownBasenames(dir) {
|
|
58
54
|
if (!fs.existsSync(dir))
|
|
59
55
|
return [];
|
|
@@ -74,8 +70,7 @@ function listSkills() {
|
|
|
74
70
|
});
|
|
75
71
|
const baseSkills = listMarkdownBasenames(getSkillsDir());
|
|
76
72
|
const protocolMirrors = listMarkdownBasenames(getProtocolMirrorDir());
|
|
77
|
-
const
|
|
78
|
-
const skills = [...new Set([...baseSkills, ...protocolMirrors, ...canonicalProtocols])].sort();
|
|
73
|
+
const skills = [...new Set([...baseSkills, ...protocolMirrors])].sort();
|
|
79
74
|
(0, runtime_1.emitNervesEvent)({
|
|
80
75
|
event: "repertoire.load_end",
|
|
81
76
|
component: "repertoire",
|
|
@@ -93,7 +88,6 @@ function loadSkill(skillName) {
|
|
|
93
88
|
});
|
|
94
89
|
const directSkillPath = path.join(getSkillsDir(), `${skillName}.md`);
|
|
95
90
|
const protocolMirrorPath = path.join(getProtocolMirrorDir(), `${skillName}.md`);
|
|
96
|
-
const canonicalProtocolPath = path.join(getCanonicalProtocolsDir(), `${skillName}.md`);
|
|
97
91
|
let resolvedPath = null;
|
|
98
92
|
// 1) Direct agent skill.
|
|
99
93
|
if (fs.existsSync(directSkillPath)) {
|
|
@@ -103,22 +97,6 @@ function loadSkill(skillName) {
|
|
|
103
97
|
else if (fs.existsSync(protocolMirrorPath)) {
|
|
104
98
|
resolvedPath = protocolMirrorPath;
|
|
105
99
|
}
|
|
106
|
-
// 3) Canonical protocol fallback.
|
|
107
|
-
else if (fs.existsSync(canonicalProtocolPath)) {
|
|
108
|
-
(0, runtime_1.emitNervesEvent)({
|
|
109
|
-
level: "warn",
|
|
110
|
-
event: "repertoire.error",
|
|
111
|
-
component: "repertoire",
|
|
112
|
-
message: "protocol mirror missing; using canonical fallback",
|
|
113
|
-
meta: {
|
|
114
|
-
operation: "loadSkill",
|
|
115
|
-
skill: skillName,
|
|
116
|
-
mirrorPath: protocolMirrorPath,
|
|
117
|
-
canonicalPath: canonicalProtocolPath,
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
resolvedPath = canonicalProtocolPath;
|
|
121
|
-
}
|
|
122
100
|
if (!resolvedPath) {
|
|
123
101
|
(0, runtime_1.emitNervesEvent)({
|
|
124
102
|
level: "error",
|
|
@@ -128,13 +106,12 @@ function loadSkill(skillName) {
|
|
|
128
106
|
meta: {
|
|
129
107
|
operation: "loadSkill",
|
|
130
108
|
skill: skillName,
|
|
131
|
-
checkedPaths: [directSkillPath, protocolMirrorPath
|
|
109
|
+
checkedPaths: [directSkillPath, protocolMirrorPath],
|
|
132
110
|
},
|
|
133
111
|
});
|
|
134
112
|
throw new Error(`skill '${skillName}' not found in:\n` +
|
|
135
113
|
`- ${directSkillPath}\n` +
|
|
136
|
-
`- ${protocolMirrorPath}
|
|
137
|
-
`- ${canonicalProtocolPath}`);
|
|
114
|
+
`- ${protocolMirrorPath}`);
|
|
138
115
|
}
|
|
139
116
|
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
140
117
|
if (!loadedSkills.includes(skillName)) {
|
|
@@ -59,6 +59,12 @@ function activeSessionLines(tasks) {
|
|
|
59
59
|
});
|
|
60
60
|
return active.map((task) => task.stem).sort();
|
|
61
61
|
}
|
|
62
|
+
function activeBridgeLines(tasks) {
|
|
63
|
+
return tasks
|
|
64
|
+
.filter((task) => typeof task.frontmatter.active_bridge === "string" && String(task.frontmatter.active_bridge).trim())
|
|
65
|
+
.map((task) => `${task.stem} -> ${String(task.frontmatter.active_bridge).trim()}`)
|
|
66
|
+
.sort();
|
|
67
|
+
}
|
|
62
68
|
function actionRequired(index, byStatus) {
|
|
63
69
|
const actions = [...index.parseErrors, ...index.invalidFilenames.map((filePath) => `bad filename: ${filePath}`)];
|
|
64
70
|
if (byStatus.blocked.length > 0) {
|
|
@@ -99,6 +105,11 @@ function buildTaskBoard(index) {
|
|
|
99
105
|
fullLines.push("## active sessions");
|
|
100
106
|
fullLines.push(active.map((line) => `- ${line}`).join("\n"));
|
|
101
107
|
}
|
|
108
|
+
const activeBridges = activeBridgeLines(index.tasks);
|
|
109
|
+
if (activeBridges.length > 0) {
|
|
110
|
+
fullLines.push("## active bridges");
|
|
111
|
+
fullLines.push(activeBridges.map((line) => `- ${line}`).join("\n"));
|
|
112
|
+
}
|
|
102
113
|
return {
|
|
103
114
|
compact,
|
|
104
115
|
full: fullLines.join("\n\n"),
|
|
@@ -106,6 +117,7 @@ function buildTaskBoard(index) {
|
|
|
106
117
|
actionRequired: actionRequired(index, byStatus),
|
|
107
118
|
unresolvedDependencies: unresolved,
|
|
108
119
|
activeSessions: active,
|
|
120
|
+
activeBridges,
|
|
109
121
|
};
|
|
110
122
|
}
|
|
111
123
|
function boardStatus(board, status) {
|
|
@@ -37,6 +37,7 @@ exports.getTaskModule = getTaskModule;
|
|
|
37
37
|
exports.resetTaskModule = resetTaskModule;
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const config_1 = require("../../heart/config");
|
|
40
41
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
42
|
const board_1 = require("./board");
|
|
42
43
|
const lifecycle_1 = require("./lifecycle");
|
|
@@ -55,14 +56,6 @@ function formatStemTimestamp(now = new Date()) {
|
|
|
55
56
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
56
57
|
return `${year}-${month}-${day}-${hours}${minutes}`;
|
|
57
58
|
}
|
|
58
|
-
function slugify(input) {
|
|
59
|
-
return input
|
|
60
|
-
.toLowerCase()
|
|
61
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
62
|
-
.replace(/^-+/, "")
|
|
63
|
-
.replace(/-+$/, "")
|
|
64
|
-
.slice(0, 64);
|
|
65
|
-
}
|
|
66
59
|
function findTask(index, nameOrStem) {
|
|
67
60
|
return (index.tasks.find((task) => task.stem === nameOrStem || task.name === nameOrStem) ??
|
|
68
61
|
index.tasks.find((task) => task.stem.endsWith(nameOrStem)) ??
|
|
@@ -98,7 +91,7 @@ class FileTaskModule {
|
|
|
98
91
|
throw new Error(`invalid task status: ${String(input.status)}`);
|
|
99
92
|
}
|
|
100
93
|
const collection = (0, transitions_1.canonicalCollectionForTaskType)(type);
|
|
101
|
-
const stem = `${formatStemTimestamp()}-${slugify(input.title) || "task"}`;
|
|
94
|
+
const stem = `${formatStemTimestamp()}-${(0, config_1.slugify)(input.title).slice(0, 64) || "task"}`;
|
|
102
95
|
const filename = `${stem}.md`;
|
|
103
96
|
const root = (0, scanner_1.getTaskRoot)();
|
|
104
97
|
const filePath = path.join(root, collection, filename);
|
|
@@ -122,6 +115,12 @@ class FileTaskModule {
|
|
|
122
115
|
frontmatter.parent_task = null;
|
|
123
116
|
frontmatter.depends_on = [];
|
|
124
117
|
}
|
|
118
|
+
if (input.activeBridge && input.activeBridge.trim()) {
|
|
119
|
+
frontmatter.active_bridge = input.activeBridge.trim();
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(input.bridgeSessions) && input.bridgeSessions.length > 0) {
|
|
122
|
+
frontmatter.bridge_sessions = input.bridgeSessions.filter((value) => typeof value === "string" && value.trim());
|
|
123
|
+
}
|
|
125
124
|
const content = (0, parser_1.renderTaskFile)(frontmatter, input.body);
|
|
126
125
|
const validation = (0, middleware_1.validateWrite)(filePath, content);
|
|
127
126
|
if (!validation.ok) {
|
|
@@ -132,6 +131,21 @@ class FileTaskModule {
|
|
|
132
131
|
(0, scanner_1.clearTaskScanCache)();
|
|
133
132
|
return filePath;
|
|
134
133
|
}
|
|
134
|
+
bindBridge(name, input) {
|
|
135
|
+
const task = this.getTask(name);
|
|
136
|
+
if (!task) {
|
|
137
|
+
return { ok: false, reason: `task not found: ${name}` };
|
|
138
|
+
}
|
|
139
|
+
const content = fs.readFileSync(task.path, "utf-8");
|
|
140
|
+
const parsed = (0, parser_1.parseTaskFile)(content, task.path);
|
|
141
|
+
const frontmatter = removeRuntimeFrontmatter(parsed.frontmatter);
|
|
142
|
+
frontmatter.active_bridge = input.bridgeId.trim();
|
|
143
|
+
frontmatter.bridge_sessions = input.sessionRefs.filter((value) => value.trim().length > 0);
|
|
144
|
+
frontmatter.updated = formatDate();
|
|
145
|
+
fs.writeFileSync(task.path, (0, parser_1.renderTaskFile)(frontmatter, parsed.body), "utf-8");
|
|
146
|
+
(0, scanner_1.clearTaskScanCache)();
|
|
147
|
+
return { ok: true, path: task.path };
|
|
148
|
+
}
|
|
135
149
|
updateStatus(name, toStatus) {
|
|
136
150
|
const normalized = (0, transitions_1.normalizeTaskStatus)(toStatus);
|
|
137
151
|
if (!normalized) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TASK_REQUIRED_TEMPLATE_FIELDS = exports.TASK_FILENAME_PATTERN = exports.
|
|
3
|
+
exports.TASK_REQUIRED_TEMPLATE_FIELDS = exports.TASK_FILENAME_PATTERN = exports.TASK_RESERVED_DIRECTORIES = exports.TASK_TYPE_TO_COLLECTION = exports.TASK_CANONICAL_COLLECTIONS = exports.TASK_CANONICAL_TYPES = exports.TASK_STATUS_TRANSITIONS = exports.TASK_VALID_STATUSES = void 0;
|
|
4
4
|
exports.canonicalCollectionForTaskType = canonicalCollectionForTaskType;
|
|
5
5
|
exports.normalizeTaskType = normalizeTaskType;
|
|
6
6
|
exports.normalizeTaskStatus = normalizeTaskStatus;
|
|
@@ -42,7 +42,6 @@ exports.TASK_TYPE_TO_COLLECTION = {
|
|
|
42
42
|
habit: "habits",
|
|
43
43
|
};
|
|
44
44
|
exports.TASK_RESERVED_DIRECTORIES = ["templates", ".trash", "archive"];
|
|
45
|
-
exports.TASK_STEM_PATTERN = /^\d{4}-\d{2}-\d{2}-\d{4}-[a-z0-9][a-z0-9-]*$/;
|
|
46
45
|
exports.TASK_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}-\d{4}-[a-z0-9][a-z0-9-]*\.md$/;
|
|
47
46
|
exports.TASK_REQUIRED_TEMPLATE_FIELDS = {
|
|
48
47
|
"one-shot": [
|