om-memory-system 4.29.1 → 4.31.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 +1 -0
- package/dist/adapters/opencode/conversation.js +22 -17
- package/dist/adapters/opencode/skills-tool.d.ts +3 -0
- package/dist/adapters/opencode/skills-tool.js +31 -0
- package/dist/adapters/pi/capture.js +39 -23
- package/dist/adapters/pi/extension.d.ts +1 -0
- package/dist/adapters/pi/extension.js +3 -0
- package/dist/adapters/pi/skills-tool.d.ts +3 -0
- package/dist/adapters/pi/skills-tool.js +34 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/skills-command.d.ts +12 -0
- package/dist/cli/skills-command.js +242 -0
- package/dist/cli/skills-record-past.d.ts +5 -0
- package/dist/cli/skills-record-past.js +85 -0
- package/dist/config-template.d.ts +1 -1
- package/dist/config-template.js +12 -0
- package/dist/config.d.ts +64 -48
- package/dist/config.js +28 -5
- package/dist/core/host.d.ts +7 -0
- package/dist/core/profile-backoff.d.ts +16 -8
- package/dist/core/profile-backoff.js +25 -16
- package/dist/core/skill-agent-modes.d.ts +4 -0
- package/dist/core/skill-agent-modes.js +145 -0
- package/dist/core/skill-operation-reply.d.ts +16 -0
- package/dist/core/skill-operation-reply.js +35 -0
- package/dist/core/skill-operations.d.ts +58 -0
- package/dist/core/skill-operations.js +296 -0
- package/dist/core/skills-tool.d.ts +90 -0
- package/dist/core/skills-tool.js +64 -0
- package/dist/core/tool-input.d.ts +7 -0
- package/dist/core/tool-input.js +21 -0
- package/dist/core/tool-outcome.d.ts +13 -0
- package/dist/core/tool-outcome.js +22 -0
- package/dist/core/turn-outputs.d.ts +30 -0
- package/dist/core/turn-outputs.js +22 -0
- package/dist/importer/auto-backfill.js +13 -1
- package/dist/importer/claude-conversation.d.ts +1 -0
- package/dist/importer/claude-conversation.js +44 -10
- package/dist/importer/claude-hook-api.d.ts +0 -8
- package/dist/importer/claude-hook-api.js +36 -65
- package/dist/importer/claude-import.js +3 -1
- package/dist/importer/claude-reader.d.ts +2 -0
- package/dist/importer/claude-reader.js +2 -2
- package/dist/importer/claude-turn-units.d.ts +26 -0
- package/dist/importer/claude-turn-units.js +85 -0
- package/dist/importer/import-args.d.ts +3 -0
- package/dist/importer/import-args.js +8 -2
- package/dist/importer/import-project.d.ts +6 -0
- package/dist/importer/import-project.js +9 -0
- package/dist/importer/import-runs.d.ts +8 -2
- package/dist/importer/import-runs.js +3 -3
- package/dist/importer/import-sessions.d.ts +8 -1
- package/dist/importer/import-sessions.js +17 -4
- package/dist/importer/import-turn-outputs.d.ts +49 -0
- package/dist/importer/import-turn-outputs.js +88 -0
- package/dist/importer/importer.d.ts +5 -2
- package/dist/importer/importer.js +18 -32
- package/dist/importer/opencode-import.d.ts +2 -0
- package/dist/importer/opencode-import.js +6 -3
- package/dist/importer/opencode-reader.js +11 -5
- package/dist/importer/pi-conversation.d.ts +5 -0
- package/dist/importer/pi-conversation.js +33 -9
- package/dist/importer/profile-catch-up.d.ts +1 -1
- package/dist/importer/record-past-turns.d.ts +28 -0
- package/dist/importer/record-past-turns.js +63 -0
- package/dist/importer/run-import.d.ts +4 -0
- package/dist/importer/run-import.js +24 -3
- package/dist/importer/web-import-api.d.ts +14 -0
- package/dist/importer/web-import-api.js +6 -0
- package/dist/importer/web-import-group.d.ts +4 -1
- package/dist/importer/web-import-group.js +26 -6
- package/dist/importer/web-import-jobs.js +6 -2
- package/dist/index.js +6 -1
- package/dist/services/ai/session/ai-session-manager.js +13 -0
- package/dist/services/auto-capture.d.ts +1 -1
- package/dist/services/auto-capture.js +71 -19
- package/dist/services/capture-attempt-store.d.ts +2 -0
- package/dist/services/capture-attempt-store.js +11 -0
- package/dist/services/chat/chat-runtime.d.ts +2 -0
- package/dist/services/chat/chat-runtime.js +3 -2
- package/dist/services/global-config-writer.js +32 -2
- package/dist/services/profile-cleanup-routes.d.ts +2 -2
- package/dist/services/profile-cleanup-routes.js +19 -2
- package/dist/services/settings-snapshot.js +7 -1
- package/dist/services/skill-learning/agent-proposals.d.ts +76 -0
- package/dist/services/skill-learning/agent-proposals.js +180 -0
- package/dist/services/skill-learning/apply.d.ts +50 -0
- package/dist/services/skill-learning/apply.js +247 -0
- package/dist/services/skill-learning/auto-review.d.ts +40 -0
- package/dist/services/skill-learning/auto-review.js +132 -0
- package/dist/services/skill-learning/draft-turns.d.ts +60 -0
- package/dist/services/skill-learning/draft-turns.js +131 -0
- package/dist/services/skill-learning/drafting.d.ts +73 -0
- package/dist/services/skill-learning/drafting.js +263 -0
- package/dist/services/skill-learning/line-diff.d.ts +6 -0
- package/dist/services/skill-learning/line-diff.js +94 -0
- package/dist/services/skill-learning/operations-deps.d.ts +7 -0
- package/dist/services/skill-learning/operations-deps.js +19 -0
- package/dist/services/skill-learning/proposal-store.d.ts +82 -0
- package/dist/services/skill-learning/proposal-store.js +164 -0
- package/dist/services/skill-learning/ranking.d.ts +36 -0
- package/dist/services/skill-learning/ranking.js +140 -0
- package/dist/services/skill-learning/settings-for.d.ts +11 -0
- package/dist/services/skill-learning/settings-for.js +21 -0
- package/dist/services/skill-learning/skill-config.d.ts +45 -0
- package/dist/services/skill-learning/skill-config.js +70 -0
- package/dist/services/skill-learning/skill-detect.d.ts +29 -0
- package/dist/services/skill-learning/skill-detect.js +130 -0
- package/dist/services/skill-learning/skill-folders.d.ts +24 -0
- package/dist/services/skill-learning/skill-folders.js +42 -0
- package/dist/services/skill-learning/skill-index.d.ts +28 -0
- package/dist/services/skill-learning/skill-index.js +118 -0
- package/dist/services/skill-learning/skill-model.d.ts +10 -0
- package/dist/services/skill-learning/skill-model.js +15 -0
- package/dist/services/skill-learning/store.d.ts +79 -0
- package/dist/services/skill-learning/store.js +245 -0
- package/dist/services/skill-learning/turn-output.d.ts +34 -0
- package/dist/services/skill-learning/turn-output.js +98 -0
- package/dist/services/skill-learning/turn-summaries.d.ts +24 -0
- package/dist/services/skill-learning/turn-summaries.js +64 -0
- package/dist/services/skill-learning/web-review.d.ts +18 -0
- package/dist/services/skill-learning/web-review.js +44 -0
- package/dist/services/skill-routes.d.ts +20 -0
- package/dist/services/skill-routes.js +249 -0
- package/dist/services/turso/connection-manager.js +11 -7
- package/dist/services/turso/table-lease.d.ts +40 -0
- package/dist/services/turso/table-lease.js +77 -0
- package/dist/services/turso/turso-db.d.ts +4 -0
- package/dist/services/turso/turso-db.js +13 -0
- package/dist/services/user-prompt/profile-catch-up-lease.d.ts +3 -22
- package/dist/services/user-prompt/profile-catch-up-lease.js +9 -65
- package/dist/services/web-jobs.d.ts +7 -0
- package/dist/services/web-jobs.js +14 -0
- package/dist/services/web-route-modules.d.ts +2 -2
- package/dist/services/web-route-modules.js +6 -1
- package/dist/services/web-server.js +13 -21
- package/dist/v2/adapter.js +13 -8
- package/dist/web/assets/index-9hu4Z7SV.css +2 -0
- package/dist/web/assets/index-BMt9gPNK.js +129 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CHBKLTiT.js +0 -126
- package/dist/web/assets/index-DsQXsY3T.css +0 -2
package/README.md
CHANGED
|
@@ -131,6 +131,7 @@ Choose models and manage access. Diagnostics show capture outcomes and component
|
|
|
131
131
|
| [Moving projects](docs/moving-projects.md) | Folder moves, backup and restore |
|
|
132
132
|
| [Updating and upgrading](docs/upgrading.md) | Updates, version pinning and older stores |
|
|
133
133
|
| [CLI reference](docs/cli.md) | Terminal commands |
|
|
134
|
+
| [Skill learning](docs/skill-learning.md) | Opt-in turn recording, skill ranking and draft review |
|
|
134
135
|
| [For developers](docs/developers.md) | Building, testing and architecture |
|
|
135
136
|
| [Contributing](CONTRIBUTING.md) | Development workflow and pull requests |
|
|
136
137
|
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
import { boundToolInput, readToolPath } from "../../core/tool-input.js";
|
|
2
|
+
import { opencodeToolOutcome } from "../../core/tool-outcome.js";
|
|
3
|
+
function formatToolInput(inputValue) {
|
|
4
|
+
if (!inputValue)
|
|
5
|
+
return "";
|
|
6
|
+
if (typeof inputValue === "string")
|
|
7
|
+
return inputValue;
|
|
8
|
+
if (typeof inputValue === "object") {
|
|
9
|
+
return Object.entries(inputValue)
|
|
10
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
11
|
+
.join(", ");
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
2
15
|
export function extractOpenCodeConversation(messages, promptMessageId) {
|
|
3
16
|
const promptIndex = messages.findIndex((message) => message.info?.id === promptMessageId);
|
|
4
17
|
if (promptIndex === -1)
|
|
@@ -28,22 +41,14 @@ export function extractOpenCodeConversation(messages, promptMessageId) {
|
|
|
28
41
|
}
|
|
29
42
|
for (const tool of message.parts.filter((part) => part.type === "tool")) {
|
|
30
43
|
const name = tool.tool || "unknown";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
40
|
-
.join(", ");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (input.length > MAX_TOOL_INPUT_LENGTH) {
|
|
44
|
-
input = input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
|
|
45
|
-
}
|
|
46
|
-
toolCalls.push({ name, input });
|
|
44
|
+
const input = boundToolInput(formatToolInput(tool.state?.input));
|
|
45
|
+
const path = readToolPath(name, tool.state?.input);
|
|
46
|
+
toolCalls.push({
|
|
47
|
+
name,
|
|
48
|
+
input,
|
|
49
|
+
...opencodeToolOutcome(tool.state),
|
|
50
|
+
...(path === undefined ? {} : { path }),
|
|
51
|
+
});
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
return { textResponses, toolCalls, sourceEntryIds };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { SKILLS_TOOL_DESCRIPTION, SKILLS_TOOL_MODES, runSkillsTool, } from "../../core/skills-tool.js";
|
|
3
|
+
/** The OpenCode `skills` tool. V2 reaches it through the V1 plugin's `tool.skills`. */
|
|
4
|
+
export function opencodeSkillsTool(directory) {
|
|
5
|
+
return tool({
|
|
6
|
+
description: SKILLS_TOOL_DESCRIPTION,
|
|
7
|
+
args: {
|
|
8
|
+
mode: tool.schema.enum(SKILLS_TOOL_MODES).optional(),
|
|
9
|
+
skill: tool.schema.string().optional(),
|
|
10
|
+
id: tool.schema.string().optional(),
|
|
11
|
+
state: tool.schema.string().optional(),
|
|
12
|
+
order: tool.schema.string().optional(),
|
|
13
|
+
folder: tool.schema.string().optional(),
|
|
14
|
+
idea: tool.schema
|
|
15
|
+
.object({
|
|
16
|
+
name: tool.schema.string().optional(),
|
|
17
|
+
description: tool.schema.string().optional(),
|
|
18
|
+
reason: tool.schema.string().optional(),
|
|
19
|
+
evidenceTurnIds: tool.schema.array(tool.schema.number()).optional(),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
limit: tool.schema.number().optional(),
|
|
23
|
+
content: tool.schema.string().optional(),
|
|
24
|
+
summary: tool.schema.string().optional(),
|
|
25
|
+
evidence: tool.schema.array(tool.schema.number()).optional(),
|
|
26
|
+
},
|
|
27
|
+
async execute(args) {
|
|
28
|
+
return runSkillsTool(args, directory);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { CONFIG } from "../../config.js";
|
|
2
|
-
import { captureConversation } from "../../core/capture.js";
|
|
3
2
|
import { quickRetryDelayMs } from "../../core/capture-retry-policy.js";
|
|
3
|
+
import { handleFinishedTurn } from "../../core/turn-outputs.js";
|
|
4
4
|
import { log } from "../../services/logger.js";
|
|
5
5
|
import { memoryClient } from "../../services/client.js";
|
|
6
6
|
import { queueFailedCapture, startCaptureRetryDrain } from "../../services/capture-retry-drain.js";
|
|
7
|
-
import {
|
|
7
|
+
import { skillTurnRecorder, } from "../../services/skill-learning/settings-for.js";
|
|
8
|
+
import { extractPiConversation, } from "../../importer/pi-conversation.js";
|
|
8
9
|
export function createPiCaptureState() {
|
|
9
10
|
return { handledUserEntries: new Set(), running: false };
|
|
10
11
|
}
|
|
11
12
|
/** Try the turn up to `autoCaptureMaxRetries` times, as OpenCode does, before it counts as failed. */
|
|
12
|
-
async function captureWithQuickRetries(unit, provider) {
|
|
13
|
+
async function captureWithQuickRetries(unit, provider, recorder) {
|
|
13
14
|
const maxRetries = Math.max(1, CONFIG.autoCaptureMaxRetries ?? 3);
|
|
14
15
|
for (let attempt = 1;; attempt++) {
|
|
15
16
|
try {
|
|
16
|
-
|
|
17
|
+
// A repeated attempt records the same turn key, which the store drops.
|
|
18
|
+
return await handleFinishedTurn(unit, { memories: true, skills: recorder !== undefined }, { provider, recordSkillTurn: recorder });
|
|
17
19
|
}
|
|
18
20
|
catch (error) {
|
|
19
21
|
if (attempt >= maxRetries)
|
|
@@ -25,6 +27,32 @@ async function captureWithQuickRetries(unit, provider) {
|
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
}
|
|
30
|
+
function piWorkUnit(input, window) {
|
|
31
|
+
return {
|
|
32
|
+
host: "pi",
|
|
33
|
+
hostSessionId: input.sessionId,
|
|
34
|
+
sourceType: "live-capture",
|
|
35
|
+
projectDirectory: input.directory,
|
|
36
|
+
userPrompt: window.userPrompt,
|
|
37
|
+
promptId: window.userEntryId,
|
|
38
|
+
prompt: {
|
|
39
|
+
id: window.userEntryId,
|
|
40
|
+
providerId: input.prompt?.providerId ?? null,
|
|
41
|
+
modelId: input.prompt?.modelId ?? null,
|
|
42
|
+
},
|
|
43
|
+
textResponses: window.textResponses,
|
|
44
|
+
toolCalls: window.toolCalls,
|
|
45
|
+
sourceEntryIds: window.sourceEntryIds,
|
|
46
|
+
...(window.sourceTimestamp !== undefined ? { sourceTimestamp: window.sourceTimestamp } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Memory capture is off or not ready: record the settled turn for skill learning only. */
|
|
50
|
+
async function recordSkillTurnOnly(input, window = extractPiConversation(input.entries)) {
|
|
51
|
+
const recorder = skillTurnRecorder(input.directory);
|
|
52
|
+
if (!recorder || !window)
|
|
53
|
+
return;
|
|
54
|
+
await handleFinishedTurn(piWorkUnit(input, window), { memories: false, skills: true }, { recordSkillTurn: recorder });
|
|
55
|
+
}
|
|
28
56
|
/**
|
|
29
57
|
* Capture the work unit completed by the most recent user prompt on the
|
|
30
58
|
* settled branch. Safe to call for every `agent_settled` event: a work unit is
|
|
@@ -32,8 +60,11 @@ async function captureWithQuickRetries(unit, provider) {
|
|
|
32
60
|
* compaction continuation, and repeated settled events never double-capture.
|
|
33
61
|
*/
|
|
34
62
|
export async function capturePiSettledWorkUnit(input) {
|
|
35
|
-
if (!CONFIG.autoCaptureEnabled)
|
|
63
|
+
if (!CONFIG.autoCaptureEnabled) {
|
|
64
|
+
// Skill learning records turns even when memory capture is off (ADR-036).
|
|
65
|
+
await recordSkillTurnOnly(input);
|
|
36
66
|
return { status: "skipped", reason: "disabled" };
|
|
67
|
+
}
|
|
37
68
|
if (input.state.running)
|
|
38
69
|
return { status: "skipped", reason: "busy" };
|
|
39
70
|
input.state.running = true;
|
|
@@ -47,26 +78,11 @@ export async function capturePiSettledWorkUnit(input) {
|
|
|
47
78
|
}
|
|
48
79
|
const embeddingInitError = memoryClient.getEmbeddingInitError?.();
|
|
49
80
|
if (embeddingInitError) {
|
|
81
|
+
await recordSkillTurnOnly(input, window);
|
|
50
82
|
return { status: "skipped", reason: "not-ready" };
|
|
51
83
|
}
|
|
52
|
-
workUnit =
|
|
53
|
-
|
|
54
|
-
hostSessionId: input.sessionId,
|
|
55
|
-
sourceType: "live-capture",
|
|
56
|
-
projectDirectory: input.directory,
|
|
57
|
-
userPrompt: window.userPrompt,
|
|
58
|
-
promptId: window.userEntryId,
|
|
59
|
-
prompt: {
|
|
60
|
-
id: window.userEntryId,
|
|
61
|
-
providerId: input.prompt?.providerId ?? null,
|
|
62
|
-
modelId: input.prompt?.modelId ?? null,
|
|
63
|
-
},
|
|
64
|
-
textResponses: window.textResponses,
|
|
65
|
-
toolCalls: window.toolCalls,
|
|
66
|
-
sourceEntryIds: window.sourceEntryIds,
|
|
67
|
-
...(window.sourceTimestamp !== undefined ? { sourceTimestamp: window.sourceTimestamp } : {}),
|
|
68
|
-
};
|
|
69
|
-
const captureResult = await captureWithQuickRetries(workUnit, input.provider);
|
|
84
|
+
workUnit = piWorkUnit(input, window);
|
|
85
|
+
const captureResult = await captureWithQuickRetries(workUnit, input.provider, skillTurnRecorder(input.directory));
|
|
70
86
|
input.state.handledUserEntries.add(window.userEntryId);
|
|
71
87
|
if (captureResult.status === "skipped") {
|
|
72
88
|
log("Pi auto-capture skipped by extractor", {
|
|
@@ -11,6 +11,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
11
11
|
* - `session_shutdown` -> idempotent session-scoped cleanup
|
|
12
12
|
* - `memory` tool -> shared add/search/profile/list/forget/help and
|
|
13
13
|
* portability operations
|
|
14
|
+
* - `skills` tool -> shared skill-learning operations (skills-tool.ts)
|
|
14
15
|
*
|
|
15
16
|
* Pi types are imported type-only: the compiled extension carries no Pi
|
|
16
17
|
* runtime dependency and uses the host Pi runtime provided at load time.
|
|
@@ -10,6 +10,7 @@ import { memoryClient } from "../../services/client.js";
|
|
|
10
10
|
import { capturePiSettledWorkUnit, createPiCaptureState } from "./capture.js";
|
|
11
11
|
import { createPiLiveModels } from "./live-model.js";
|
|
12
12
|
import { registerPiHistoryImportCommand } from "./import-command.js";
|
|
13
|
+
import { registerPiSkillsTool } from "./skills-tool.js";
|
|
13
14
|
import { performPiProfileLearning } from "./profile.js";
|
|
14
15
|
import { buildRetrievalSection, retrievalWrapperBytes, wrapRetrievalSection, } from "../../core/retrieval.js";
|
|
15
16
|
import { DEFAULT_RETRIEVAL_MAX_TOKENS, tokensToByteCeiling } from "../../core/context-budget.js";
|
|
@@ -65,6 +66,7 @@ function lastSettledUserPrompt(entries) {
|
|
|
65
66
|
* - `session_shutdown` -> idempotent session-scoped cleanup
|
|
66
67
|
* - `memory` tool -> shared add/search/profile/list/forget/help and
|
|
67
68
|
* portability operations
|
|
69
|
+
* - `skills` tool -> shared skill-learning operations (skills-tool.ts)
|
|
68
70
|
*
|
|
69
71
|
* Pi types are imported type-only: the compiled extension carries no Pi
|
|
70
72
|
* runtime dependency and uses the host Pi runtime provided at load time.
|
|
@@ -348,5 +350,6 @@ export default function ommsPiExtension(pi) {
|
|
|
348
350
|
};
|
|
349
351
|
},
|
|
350
352
|
});
|
|
353
|
+
registerPiSkillsTool(pi);
|
|
351
354
|
registerPiHistoryImportCommand(pi, () => latestCtx);
|
|
352
355
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { SKILLS_TOOL_DESCRIPTION, SKILLS_TOOL_MODES, runSkillsTool, } from "../../core/skills-tool.js";
|
|
3
|
+
/** The Pi `skills` tool, registered beside `memory`. */
|
|
4
|
+
export function registerPiSkillsTool(pi) {
|
|
5
|
+
pi.registerTool({
|
|
6
|
+
name: "skills",
|
|
7
|
+
label: "Skills",
|
|
8
|
+
description: SKILLS_TOOL_DESCRIPTION,
|
|
9
|
+
parameters: Type.Object({
|
|
10
|
+
mode: Type.Optional(Type.Union(SKILLS_TOOL_MODES.map((mode) => Type.Literal(mode)))),
|
|
11
|
+
skill: Type.Optional(Type.String()),
|
|
12
|
+
id: Type.Optional(Type.String()),
|
|
13
|
+
state: Type.Optional(Type.String()),
|
|
14
|
+
order: Type.Optional(Type.String()),
|
|
15
|
+
folder: Type.Optional(Type.String()),
|
|
16
|
+
idea: Type.Optional(Type.Object({
|
|
17
|
+
name: Type.Optional(Type.String()),
|
|
18
|
+
description: Type.Optional(Type.String()),
|
|
19
|
+
reason: Type.Optional(Type.String()),
|
|
20
|
+
evidenceTurnIds: Type.Optional(Type.Array(Type.Number())),
|
|
21
|
+
})),
|
|
22
|
+
limit: Type.Optional(Type.Number()),
|
|
23
|
+
content: Type.Optional(Type.String()),
|
|
24
|
+
summary: Type.Optional(Type.String()),
|
|
25
|
+
evidence: Type.Optional(Type.Array(Type.Number())),
|
|
26
|
+
}),
|
|
27
|
+
async execute(_toolCallId, args, _signal, _onUpdate, ctx) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: await runSkillsTool(args, ctx.cwd) }],
|
|
30
|
+
details: {},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -17,6 +17,7 @@ Commands:
|
|
|
17
17
|
web install|uninstall|status Manage the web app login item
|
|
18
18
|
web update Update the global install and restart the web app
|
|
19
19
|
memory <mode> [options] Search, add, list, or forget memories (run with --help)
|
|
20
|
+
skills <mode> [options] List skills, review proposals, record or delete turns (run with --help)
|
|
20
21
|
profile-catch-up Analyse every prompt waiting for profile learning (run with --help)
|
|
21
22
|
profile-normalise-categories Preview or confirm profile category renames (run with --help)
|
|
22
23
|
profile-cleanup-categories Group category names by meaning; preview, apply or undo (run with --help)
|
|
@@ -65,6 +66,10 @@ export async function runCli(argv) {
|
|
|
65
66
|
const { runMemoryCommand } = await import("./memory-command.js");
|
|
66
67
|
return await runMemoryCommand(argv.slice(1));
|
|
67
68
|
}
|
|
69
|
+
if (argv[0] === "skills") {
|
|
70
|
+
const { runSkillsCommand } = await import("./skills-command.js");
|
|
71
|
+
return await runSkillsCommand(argv.slice(1));
|
|
72
|
+
}
|
|
68
73
|
if (argv[0] === "profile-catch-up") {
|
|
69
74
|
const { runProfileCatchUpCommand } = await import("./profile-catch-up-command.js");
|
|
70
75
|
return await runProfileCatchUpCommand(argv.slice(1));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SkillOperationArgs } from "../core/skill-operations.js";
|
|
2
|
+
type Print = (line: string) => void;
|
|
3
|
+
type AppliedBy = "person" | "agent";
|
|
4
|
+
/** Parse the shared modes' options. Throws on bad input or an unreadable content file. */
|
|
5
|
+
export declare function parseOperationArgs(argv: string[]): {
|
|
6
|
+
args: SkillOperationArgs;
|
|
7
|
+
json: boolean;
|
|
8
|
+
appliedBy: AppliedBy;
|
|
9
|
+
directory?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function runSkillsCommand(argv: string[], print?: Print, printError?: Print): Promise<number>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* `om-memory-system skills <mode>`. Each mode lives in its own handler; this
|
|
4
|
+
* file only dispatches. Logs and progress go to stderr, results to stdout.
|
|
5
|
+
* The shared modes (list to help) run `runSkillOperation`, the same code as the
|
|
6
|
+
* hosts' `skills` tool, and record `person` on apply. The Claude Code tool
|
|
7
|
+
* passes the hidden `--applied-by agent`, so its applies record `agent`.
|
|
8
|
+
*/
|
|
9
|
+
const usage = `Usage: om-memory-system skills <mode> [options]
|
|
10
|
+
|
|
11
|
+
Modes:
|
|
12
|
+
list Skills ranked by use in the last 30 days
|
|
13
|
+
ideas Up to three new skill ideas from recorded turns
|
|
14
|
+
draft Draft an improvement (--skill) or a new skill
|
|
15
|
+
(--idea-name, --idea-description, --folder) as a pending proposal
|
|
16
|
+
evidence A skill's SKILL.md, numbers and recorded turns (--skill, --limit)
|
|
17
|
+
propose Save a fixed SKILL.md as a pending proposal, with no model call
|
|
18
|
+
(--skill or the idea options, --content-file, --summary, --evidence)
|
|
19
|
+
proposals List proposals, newest first (--state)
|
|
20
|
+
show One proposal with its summary, evidence and diff (--id)
|
|
21
|
+
apply Write a pending proposal to its SKILL.md (--id)
|
|
22
|
+
reject Reject a pending proposal (--id)
|
|
23
|
+
undo Restore the file an applied proposal changed (--id)
|
|
24
|
+
help List the modes
|
|
25
|
+
record-past [--yes] Record past turns from every set-up host's history; no model call
|
|
26
|
+
delete-turns [--yes] Delete every recorded turn; proposals and saved skill versions stay
|
|
27
|
+
|
|
28
|
+
Options for the modes from list to help:
|
|
29
|
+
--skill <name|path> Skill name, or the full path of its SKILL.md (draft, evidence, propose)
|
|
30
|
+
--idea-name <name> Name of a new skill idea (draft, propose)
|
|
31
|
+
--idea-description <text> Description of a new skill idea (draft, propose)
|
|
32
|
+
--idea-reason <text> Why the idea is needed, from ideas (draft)
|
|
33
|
+
--idea-evidence <ids> The idea's evidence turn ids, such as 12,14 (draft)
|
|
34
|
+
--folder <path> Watched folder for a new skill (draft, propose)
|
|
35
|
+
--limit <n> Recorded turns to show, 1 to 20; default 10 (evidence)
|
|
36
|
+
--content-file <path> File with the full new SKILL.md; - reads standard input (propose)
|
|
37
|
+
--summary <text> What changed and why (propose)
|
|
38
|
+
--evidence <ids> Turn ids that show the need, such as 12,14 (propose)
|
|
39
|
+
--state <state> pending, applied, stale, rejected or undone (proposals)
|
|
40
|
+
--order <order> uses (default) or attention (list)
|
|
41
|
+
--id <id> Proposal id (show, apply, reject, undo)
|
|
42
|
+
--directory <path> Project folder (default: the working folder)
|
|
43
|
+
--json Print the result as one JSON document
|
|
44
|
+
|
|
45
|
+
Without --yes, record-past and delete-turns print counts and change nothing.
|
|
46
|
+
Run record-past or delete-turns with --help to see its options.`;
|
|
47
|
+
const OPERATION_MODES = new Set([
|
|
48
|
+
"list",
|
|
49
|
+
"ideas",
|
|
50
|
+
"draft",
|
|
51
|
+
"evidence",
|
|
52
|
+
"propose",
|
|
53
|
+
"proposals",
|
|
54
|
+
"show",
|
|
55
|
+
"apply",
|
|
56
|
+
"reject",
|
|
57
|
+
"undo",
|
|
58
|
+
"help",
|
|
59
|
+
]);
|
|
60
|
+
const VALUE_FLAGS = new Set([
|
|
61
|
+
"--skill",
|
|
62
|
+
"--id",
|
|
63
|
+
"--state",
|
|
64
|
+
"--order",
|
|
65
|
+
"--folder",
|
|
66
|
+
"--idea-name",
|
|
67
|
+
"--idea-description",
|
|
68
|
+
"--idea-reason",
|
|
69
|
+
"--idea-evidence",
|
|
70
|
+
"--directory",
|
|
71
|
+
"--limit",
|
|
72
|
+
"--content-file",
|
|
73
|
+
"--summary",
|
|
74
|
+
"--evidence",
|
|
75
|
+
// Hidden: the Claude Code tool passes `--applied-by agent`.
|
|
76
|
+
"--applied-by",
|
|
77
|
+
]);
|
|
78
|
+
/** Parse the shared modes' options. Throws on bad input or an unreadable content file. */
|
|
79
|
+
export function parseOperationArgs(argv) {
|
|
80
|
+
const values = {};
|
|
81
|
+
let json = false;
|
|
82
|
+
for (let index = 0; index < argv.length; index++) {
|
|
83
|
+
const token = argv[index];
|
|
84
|
+
if (token === "--json") {
|
|
85
|
+
json = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const equals = token.indexOf("=");
|
|
89
|
+
const flag = equals === -1 ? token : token.slice(0, equals);
|
|
90
|
+
if (!flag.startsWith("--"))
|
|
91
|
+
throw new Error(`Unexpected argument "${token}"`);
|
|
92
|
+
if (!VALUE_FLAGS.has(flag))
|
|
93
|
+
throw new Error(`Unknown option ${flag}`);
|
|
94
|
+
// A following flag is not a value; literal text that starts with "--" needs `--flag=value`.
|
|
95
|
+
const next = argv[index + 1];
|
|
96
|
+
const value = equals !== -1
|
|
97
|
+
? token.slice(equals + 1)
|
|
98
|
+
: next !== undefined && !next.startsWith("--")
|
|
99
|
+
? argv[++index]
|
|
100
|
+
: undefined;
|
|
101
|
+
if (value === undefined)
|
|
102
|
+
throw new Error(`${flag} needs a value`);
|
|
103
|
+
values[flag] = value;
|
|
104
|
+
}
|
|
105
|
+
const args = {
|
|
106
|
+
skill: values["--skill"],
|
|
107
|
+
id: values["--id"],
|
|
108
|
+
state: values["--state"],
|
|
109
|
+
order: values["--order"],
|
|
110
|
+
folder: values["--folder"],
|
|
111
|
+
limit: values["--limit"],
|
|
112
|
+
summary: values["--summary"],
|
|
113
|
+
evidence: values["--evidence"],
|
|
114
|
+
};
|
|
115
|
+
const contentFile = values["--content-file"];
|
|
116
|
+
if (contentFile !== undefined) {
|
|
117
|
+
try {
|
|
118
|
+
args.content = readFileSync(contentFile === "-" ? 0 : contentFile, "utf8");
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
throw new Error(`Cannot read --content-file ${contentFile}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const appliedBy = values["--applied-by"];
|
|
125
|
+
if (appliedBy !== undefined && appliedBy !== "agent") {
|
|
126
|
+
throw new Error("--applied-by accepts only agent");
|
|
127
|
+
}
|
|
128
|
+
if (values["--idea-name"] !== undefined || values["--idea-description"] !== undefined) {
|
|
129
|
+
args.idea = { name: values["--idea-name"], description: values["--idea-description"] };
|
|
130
|
+
if (values["--idea-reason"] !== undefined)
|
|
131
|
+
args.idea.reason = values["--idea-reason"];
|
|
132
|
+
const evidence = values["--idea-evidence"];
|
|
133
|
+
if (evidence !== undefined) {
|
|
134
|
+
args.idea.evidenceTurnIds = evidence
|
|
135
|
+
.split(",")
|
|
136
|
+
.map((item) => /^\s*T?(\d+)\s*$/i.exec(item)?.[1])
|
|
137
|
+
.filter((id) => id !== undefined)
|
|
138
|
+
.map(Number);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
args,
|
|
143
|
+
json,
|
|
144
|
+
appliedBy: appliedBy === "agent" ? "agent" : "person",
|
|
145
|
+
directory: values["--directory"],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Run one shared `skills` mode for the working directory (spec: Agents and terminal reach operations). */
|
|
149
|
+
async function runOperation(mode, argv, print, printError) {
|
|
150
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
151
|
+
print(usage);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
let parsed;
|
|
155
|
+
try {
|
|
156
|
+
parsed = parseOperationArgs(argv);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
printError(`${error instanceof Error ? error.message : String(error)}\n\n${usage}`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
const directory = parsed.directory ?? process.cwd();
|
|
163
|
+
const secrets = [];
|
|
164
|
+
let result;
|
|
165
|
+
try {
|
|
166
|
+
const { CONFIG, initConfig } = await import("../config.js");
|
|
167
|
+
initConfig(directory);
|
|
168
|
+
for (const secret of [CONFIG.memoryApiKey, CONFIG.embeddingApiKey]) {
|
|
169
|
+
if (secret)
|
|
170
|
+
secrets.push(secret);
|
|
171
|
+
}
|
|
172
|
+
const { runSkillOperation } = await import("../core/skill-operations.js");
|
|
173
|
+
const { skillOperationsDeps } = await import("../services/skill-learning/operations-deps.js");
|
|
174
|
+
result = await runSkillOperation(mode, parsed.args, skillOperationsDeps(directory, parsed.appliedBy));
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
178
|
+
result = { success: false, mode, code: "error", text };
|
|
179
|
+
}
|
|
180
|
+
let output = parsed.json ? JSON.stringify(result, null, 2) : result.text;
|
|
181
|
+
for (const secret of secrets)
|
|
182
|
+
output = output.replaceAll(secret, "[redacted]");
|
|
183
|
+
print(output);
|
|
184
|
+
return result.success ? 0 : 1;
|
|
185
|
+
}
|
|
186
|
+
/** Delete every recorded turn with its tool calls and skill uses (spec: The user can delete recorded turns). */
|
|
187
|
+
async function runDeleteTurns(argv, print) {
|
|
188
|
+
let yes = false;
|
|
189
|
+
for (const flag of argv) {
|
|
190
|
+
if (flag === "--yes")
|
|
191
|
+
yes = true;
|
|
192
|
+
else if (flag === "--help" || flag === "-h") {
|
|
193
|
+
print("Usage: om-memory-system skills delete-turns [--yes]\n\nDelete every recorded turn. Proposals and saved skill versions stay.");
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
print(`Unknown option ${flag}; use --help`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const { existsSync } = await import("node:fs");
|
|
203
|
+
const { CONFIG, initConfig } = await import("../config.js");
|
|
204
|
+
initConfig(process.cwd());
|
|
205
|
+
const { SkillStore, skillsDbPath } = await import("../services/skill-learning/store.js");
|
|
206
|
+
const { skillStoragePath } = await import("../services/skill-learning/skill-folders.js");
|
|
207
|
+
const path = skillsDbPath(skillStoragePath(CONFIG));
|
|
208
|
+
const store = new SkillStore(path);
|
|
209
|
+
if (!yes) {
|
|
210
|
+
print(`Recorded turns: ${await store.countTurns()}`);
|
|
211
|
+
print("Run again with --yes to delete them. Proposals and saved skill versions stay.");
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
// init checks the schema version, so a file from a newer OMMS is never changed.
|
|
215
|
+
if (existsSync(path))
|
|
216
|
+
await store.init();
|
|
217
|
+
const deleted = await store.deleteAllTurns();
|
|
218
|
+
print(`Deleted ${deleted} recorded turns. Proposals and saved skill versions stay.`);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
print(`Delete turns failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export async function runSkillsCommand(argv, print = console.log, printError = console.error) {
|
|
227
|
+
const [mode, ...rest] = argv;
|
|
228
|
+
if (!mode || mode === "--help" || mode === "-h") {
|
|
229
|
+
print(usage);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
if (OPERATION_MODES.has(mode))
|
|
233
|
+
return runOperation(mode, rest, print, printError);
|
|
234
|
+
if (mode === "record-past") {
|
|
235
|
+
const { runSkillsRecordPast } = await import("./skills-record-past.js");
|
|
236
|
+
return runSkillsRecordPast(rest, print);
|
|
237
|
+
}
|
|
238
|
+
if (mode === "delete-turns")
|
|
239
|
+
return runDeleteTurns(rest, print);
|
|
240
|
+
print(`Unknown skills mode: ${mode}\n\n${usage}`);
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const RECORD_PAST_USAGE = "Usage: om-memory-system skills record-past [options]\n\nRecord past turns from every set-up host's history (Pi, OpenCode, Claude Code),\nall projects, with no date limit. It calls no model and is safe to run again.\nWithout --yes the command prints the counts and records nothing.\n\nOptions:\n --yes Start the run\n --dry-run Print the counts only\n --help Show this help";
|
|
2
|
+
type Print = (line: string) => void;
|
|
3
|
+
/** `om-memory-system skills record-past`: the same grouped run as the web page. */
|
|
4
|
+
export declare function runSkillsRecordPast(argv: string[], print?: Print, progress?: Print): Promise<number>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const RECORD_PAST_USAGE = `Usage: om-memory-system skills record-past [options]
|
|
2
|
+
|
|
3
|
+
Record past turns from every set-up host's history (Pi, OpenCode, Claude Code),
|
|
4
|
+
all projects, with no date limit. It calls no model and is safe to run again.
|
|
5
|
+
Without --yes the command prints the counts and records nothing.
|
|
6
|
+
|
|
7
|
+
Options:
|
|
8
|
+
--yes Start the run
|
|
9
|
+
--dry-run Print the counts only
|
|
10
|
+
--help Show this help`;
|
|
11
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
function hostLines(job, dryRun, label) {
|
|
13
|
+
return job.hosts.map((child) => {
|
|
14
|
+
const s = child.summary;
|
|
15
|
+
const counts = !s
|
|
16
|
+
? "not run"
|
|
17
|
+
: dryRun
|
|
18
|
+
? `${s.skillTurnsWouldRecord} to record, ${s.skillTurnsAlreadyRecorded} already recorded`
|
|
19
|
+
: `${s.skillTurnsRecorded} recorded, ${s.skillTurnsAlreadyPresent} already present`;
|
|
20
|
+
return `${label(child.host)}: ${counts}${child.error ? ` (${child.error})` : ""}`;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** `om-memory-system skills record-past`: the same grouped run as the web page. */
|
|
24
|
+
export async function runSkillsRecordPast(argv, print = console.log, progress = console.error) {
|
|
25
|
+
let yes = false;
|
|
26
|
+
let dryRunFlag = false;
|
|
27
|
+
for (const flag of argv) {
|
|
28
|
+
if (flag === "--yes")
|
|
29
|
+
yes = true;
|
|
30
|
+
else if (flag === "--dry-run")
|
|
31
|
+
dryRunFlag = true;
|
|
32
|
+
else if (flag === "--help" || flag === "-h") {
|
|
33
|
+
print(RECORD_PAST_USAGE);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
print(`Unknown option ${flag}; use --help`);
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const dryRun = dryRunFlag || !yes;
|
|
42
|
+
const { CONFIG, initConfig, initConfigWithLegacyMigration } = await import("../config.js");
|
|
43
|
+
try {
|
|
44
|
+
if (dryRun)
|
|
45
|
+
initConfig(process.cwd());
|
|
46
|
+
else
|
|
47
|
+
initConfigWithLegacyMigration(process.cwd());
|
|
48
|
+
if (!CONFIG.skillLearning) {
|
|
49
|
+
print("Skill learning is off. Set skillLearning to true in the global config first.");
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
const [{ importReadiness }, { startRecordPastTurns }, { SettingsImportJobs }, { hostLabel }] = await Promise.all([
|
|
53
|
+
import("../importer/import-readiness.js"),
|
|
54
|
+
import("../importer/record-past-turns.js"),
|
|
55
|
+
import("../importer/web-import-jobs.js"),
|
|
56
|
+
import("../types/host-label.js"),
|
|
57
|
+
]);
|
|
58
|
+
const readiness = await importReadiness();
|
|
59
|
+
const jobs = new SettingsImportJobs({ surface: "cli", readiness: async () => readiness });
|
|
60
|
+
await startRecordPastTurns(jobs, process.cwd(), readiness, { dryRun });
|
|
61
|
+
let job = jobs.current();
|
|
62
|
+
let shown = -1;
|
|
63
|
+
while (job.state === "running" || job.state === "cancelling") {
|
|
64
|
+
if (job.processed !== shown) {
|
|
65
|
+
shown = job.processed;
|
|
66
|
+
progress(`Reading past turns: ${job.processed}/${job.total}`);
|
|
67
|
+
}
|
|
68
|
+
await sleep(250);
|
|
69
|
+
job = jobs.current();
|
|
70
|
+
}
|
|
71
|
+
for (const line of hostLines(job, dryRun, hostLabel))
|
|
72
|
+
print(line);
|
|
73
|
+
if (job.state !== "done") {
|
|
74
|
+
print(`Record past turns ${job.state}${job.error ? `: ${job.error}` : ""}`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
if (!dryRunFlag && !yes)
|
|
78
|
+
print("Run again with --yes to start.");
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
print(`Record past turns failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
}
|