denchclaw 2.4.0 → 2.4.2
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/apps/web/.next/standalone/apps/web/.next/BUILD_ID +1 -1
- package/apps/web/.next/standalone/apps/web/.next/app-build-manifest.json +99 -99
- package/apps/web/.next/standalone/apps/web/.next/app-path-routes-manifest.json +28 -28
- package/apps/web/.next/standalone/apps/web/.next/build-manifest.json +2 -2
- package/apps/web/.next/standalone/apps/web/.next/cache/config.json +3 -3
- package/apps/web/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
- package/apps/web/.next/standalone/apps/web/.next/required-server-files.json +1 -1
- package/apps/web/.next/standalone/apps/web/.next/routes-manifest.json +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/cron/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/proxy/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/serve/[...path]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/store/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/webhooks/[...path]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/active/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/runs/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stop/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stop/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stream/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/subagents/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/jobs/[jobId]/runs/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/jobs/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/runs/[sessionId]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/runs/search-transcript/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/feedback/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/feedback/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/gateway/channels/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/gateway/chat/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/gateway/chat/stream/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/gateway/sessions/[id]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/gateway/sessions/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/memories/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/switch/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/sessions/[sessionId]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/terminal/port/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/messages/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/assets/[...path]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/browse/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/browse-file/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/context/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/copy/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/db/introspect/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/db/query/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/delete/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/delete/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/execute/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/file/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/link-preview/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/list/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/mkdir/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/move/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/actions/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/actions/runs/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/display-field/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/[id]/content/route.js +46 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/[id]/content/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/[id]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/bulk-delete/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/options/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/[fieldId]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/reorder/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/views/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/open-file/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/path-info/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/query/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/raw-file/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/rename/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/reports/execute/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/reports/execute/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/search-index/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/suggest-files/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/switch/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/switch/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/thumbnail/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/tree/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/upload/route.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/upload/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/virtual-file/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/watch/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/write-binary/route_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/page.js +2 -2
- package/apps/web/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/app-paths-manifest.json +28 -28
- package/apps/web/.next/standalone/apps/web/.next/server/chunks/6787.js +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/functions-config-manifest.json +3 -3
- package/apps/web/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/pages-manifest.json +1 -1
- package/apps/web/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/{layout-f08988a91b85294a.js → layout-63a50b5e299be036.js} +1 -1
- package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/page-95ba68825db8821d.js +1 -0
- package/apps/web/.next/standalone/apps/web/server.js +1 -1
- package/apps/web/.next/standalone/package.json +2 -1
- package/apps/web/.next/static/chunks/app/{layout-f08988a91b85294a.js → layout-63a50b5e299be036.js} +1 -1
- package/apps/web/.next/static/chunks/app/page-95ba68825db8821d.js +1 -0
- package/assets/seed/schema.sql +1 -0
- package/extensions/dench-identity/index.test.ts +136 -0
- package/extensions/dench-identity/index.ts +3 -2
- package/extensions/posthog-analytics/lib/build-env.js +1 -1
- package/extensions/vitest.config.ts +13 -0
- package/package.json +2 -1
- package/skills/crm/SKILL.md +48 -2
- package/skills/crm/documents/SKILL.md +211 -20
- package/skills/crm/duckdb-operations/SKILL.md +3 -0
- package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/page-64cad81306c18be9.js +0 -1
- package/apps/web/.next/static/chunks/app/page-64cad81306c18be9.js +0 -1
- /package/apps/web/.next/standalone/apps/web/.next/static/{xzgogMMcs02a6BQa5tMBA → TdAnmJDH-qLpIRoeLSxFc}/_buildManifest.js +0 -0
- /package/apps/web/.next/standalone/apps/web/.next/static/{xzgogMMcs02a6BQa5tMBA → TdAnmJDH-qLpIRoeLSxFc}/_ssgManifest.js +0 -0
- /package/apps/web/.next/static/{xzgogMMcs02a6BQa5tMBA → TdAnmJDH-qLpIRoeLSxFc}/_buildManifest.js +0 -0
- /package/apps/web/.next/static/{xzgogMMcs02a6BQa5tMBA → TdAnmJDH-qLpIRoeLSxFc}/_ssgManifest.js +0 -0
package/assets/seed/schema.sql
CHANGED
|
@@ -83,6 +83,7 @@ CREATE TABLE IF NOT EXISTS documents (
|
|
|
83
83
|
file_path VARCHAR NOT NULL UNIQUE,
|
|
84
84
|
parent_id VARCHAR REFERENCES documents(id),
|
|
85
85
|
parent_object_id VARCHAR REFERENCES objects(id),
|
|
86
|
+
entry_id VARCHAR REFERENCES entries(id),
|
|
86
87
|
sort_order INTEGER DEFAULT 0,
|
|
87
88
|
is_published BOOLEAN DEFAULT false,
|
|
88
89
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { buildIdentityPrompt, resolveWorkspaceDir } from "./index.ts";
|
|
3
|
+
import register from "./index.ts";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
describe("buildIdentityPrompt", () => {
|
|
7
|
+
const workspaceDir = "/home/user/workspace";
|
|
8
|
+
|
|
9
|
+
it("includes chat history path so agent can reference past conversations", () => {
|
|
10
|
+
const prompt = buildIdentityPrompt(workspaceDir);
|
|
11
|
+
expect(prompt).toContain(".openclaw/web-chat/");
|
|
12
|
+
expect(prompt).toContain(
|
|
13
|
+
path.join(workspaceDir, ".openclaw/web-chat/"),
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("includes all workspace context paths (prevents agent losing orientation)", () => {
|
|
18
|
+
const prompt = buildIdentityPrompt(workspaceDir);
|
|
19
|
+
expect(prompt).toContain(`**Root**: \`${workspaceDir}\``);
|
|
20
|
+
expect(prompt).toContain(path.join(workspaceDir, "workspace.duckdb"));
|
|
21
|
+
expect(prompt).toContain(path.join(workspaceDir, "skills"));
|
|
22
|
+
expect(prompt).toContain(path.join(workspaceDir, "apps"));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("includes CRM skill path for delegation (prevents agent using wrong skill path)", () => {
|
|
26
|
+
const prompt = buildIdentityPrompt(workspaceDir);
|
|
27
|
+
expect(prompt).toContain(
|
|
28
|
+
path.join(workspaceDir, "skills", "crm", "SKILL.md"),
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("includes exec approval policy (prevents agent stalling on exec confirmation)", () => {
|
|
33
|
+
const prompt = buildIdentityPrompt(workspaceDir);
|
|
34
|
+
expect(prompt).toContain("elevated: true");
|
|
35
|
+
expect(prompt).toContain("automatically approved");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("references DenchClaw branding, not OpenClaw (prevents identity confusion)", () => {
|
|
39
|
+
const prompt = buildIdentityPrompt(workspaceDir);
|
|
40
|
+
expect(prompt).toContain("You are **DenchClaw**");
|
|
41
|
+
expect(prompt).toContain("always use **DenchClaw** (not OpenClaw)");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("resolveWorkspaceDir", () => {
|
|
46
|
+
it("returns workspace path when config is a valid string", () => {
|
|
47
|
+
const api = { config: { agents: { defaults: { workspace: "/home/user/ws" } } } };
|
|
48
|
+
expect(resolveWorkspaceDir(api)).toBe("/home/user/ws");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns undefined when api is null (prevents crash on missing config)", () => {
|
|
52
|
+
expect(resolveWorkspaceDir(null)).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns undefined when api is undefined (prevents crash on missing config)", () => {
|
|
56
|
+
expect(resolveWorkspaceDir(undefined)).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns undefined when config chain is missing (prevents crash on partial config)", () => {
|
|
60
|
+
expect(resolveWorkspaceDir({})).toBeUndefined();
|
|
61
|
+
expect(resolveWorkspaceDir({ config: {} })).toBeUndefined();
|
|
62
|
+
expect(resolveWorkspaceDir({ config: { agents: {} } })).toBeUndefined();
|
|
63
|
+
expect(resolveWorkspaceDir({ config: { agents: { defaults: {} } } })).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns undefined when workspace is empty string (prevents empty path injection)", () => {
|
|
67
|
+
const api = { config: { agents: { defaults: { workspace: "" } } } };
|
|
68
|
+
expect(resolveWorkspaceDir(api)).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns undefined when workspace is whitespace-only (prevents whitespace path injection)", () => {
|
|
72
|
+
const api = { config: { agents: { defaults: { workspace: " " } } } };
|
|
73
|
+
expect(resolveWorkspaceDir(api)).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("returns undefined when workspace is not a string (prevents type coercion)", () => {
|
|
77
|
+
expect(resolveWorkspaceDir({ config: { agents: { defaults: { workspace: 42 } } } })).toBeUndefined();
|
|
78
|
+
expect(resolveWorkspaceDir({ config: { agents: { defaults: { workspace: true } } } })).toBeUndefined();
|
|
79
|
+
expect(resolveWorkspaceDir({ config: { agents: { defaults: { workspace: null } } } })).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("trims leading/trailing whitespace from valid paths", () => {
|
|
83
|
+
const api = { config: { agents: { defaults: { workspace: " /home/user/ws " } } } };
|
|
84
|
+
expect(resolveWorkspaceDir(api)).toBe("/home/user/ws");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("register", () => {
|
|
89
|
+
it("hooks into before_prompt_build event when enabled", () => {
|
|
90
|
+
const api = {
|
|
91
|
+
config: { plugins: { entries: {} }, agents: { defaults: { workspace: "/ws" } } },
|
|
92
|
+
on: vi.fn(),
|
|
93
|
+
};
|
|
94
|
+
register(api);
|
|
95
|
+
expect(api.on).toHaveBeenCalledWith(
|
|
96
|
+
"before_prompt_build",
|
|
97
|
+
expect.any(Function),
|
|
98
|
+
{ priority: 100 },
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("does not register handler when plugin is explicitly disabled (respects config)", () => {
|
|
103
|
+
const api = {
|
|
104
|
+
config: { plugins: { entries: { "dench-identity": { config: { enabled: false } } } } },
|
|
105
|
+
on: vi.fn(),
|
|
106
|
+
};
|
|
107
|
+
register(api);
|
|
108
|
+
expect(api.on).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("handler returns system context when workspace is configured", () => {
|
|
112
|
+
const api = {
|
|
113
|
+
config: { plugins: { entries: {} }, agents: { defaults: { workspace: "/ws" } } },
|
|
114
|
+
on: vi.fn(),
|
|
115
|
+
};
|
|
116
|
+
register(api);
|
|
117
|
+
|
|
118
|
+
const handler = api.on.mock.calls[0][1];
|
|
119
|
+
const result = handler({}, {});
|
|
120
|
+
expect(result).toEqual({
|
|
121
|
+
prependSystemContext: expect.stringContaining("DenchClaw"),
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("handler returns undefined when workspace is not configured (prevents empty prompt)", () => {
|
|
126
|
+
const api = {
|
|
127
|
+
config: { plugins: { entries: {} }, agents: { defaults: {} } },
|
|
128
|
+
on: vi.fn(),
|
|
129
|
+
};
|
|
130
|
+
register(api);
|
|
131
|
+
|
|
132
|
+
const handler = api.on.mock.calls[0][1];
|
|
133
|
+
const result = handler({}, {});
|
|
134
|
+
expect(result).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
export const id = "dench-identity";
|
|
4
4
|
|
|
5
|
-
function buildIdentityPrompt(workspaceDir: string): string {
|
|
5
|
+
export function buildIdentityPrompt(workspaceDir: string): string {
|
|
6
6
|
const skillsDir = path.join(workspaceDir, "skills");
|
|
7
7
|
const crmSkillPath = path.join(skillsDir, "crm", "SKILL.md");
|
|
8
8
|
const browserSkillPath = path.join(skillsDir, "browser", "SKILL.md");
|
|
@@ -94,6 +94,7 @@ For multi-session projects, write a session handoff summary to \`${workspaceDir}
|
|
|
94
94
|
- **Skills**: \`${skillsDir}\` — scan this directory for all available skills; new skills may be installed at any time
|
|
95
95
|
- **Apps**: \`${appsDir}\` — \`.dench.app\` folders with \`.dench.yaml\` manifests
|
|
96
96
|
- **Exec Approvals**: All commands are automatically approved by default. All exec calls must be elevated: true.
|
|
97
|
+
- **Chat History**: All chat history is stored in \`${workspaceDir}/.openclaw/web-chat/\`. This is where you can find all your past conversations and the context for the current session.
|
|
97
98
|
|
|
98
99
|
## Links
|
|
99
100
|
|
|
@@ -102,7 +103,7 @@ For multi-session projects, write a session handoff summary to \`${workspaceDir}
|
|
|
102
103
|
- Skills Store: https://skills.sh`;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
function resolveWorkspaceDir(api: any): string | undefined {
|
|
106
|
+
export function resolveWorkspaceDir(api: any): string | undefined {
|
|
106
107
|
const ws = api?.config?.agents?.defaults?.workspace;
|
|
107
108
|
return typeof ws === "string" ? ws.trim() || undefined : undefined;
|
|
108
109
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import baseConfig from "../vitest.config.ts";
|
|
2
|
+
import { defineConfig } from "vitest/config";
|
|
3
|
+
|
|
4
|
+
const base = baseConfig as unknown as Record<string, unknown>;
|
|
5
|
+
const baseTest = (baseConfig as { test?: Record<string, unknown> }).test ?? {};
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
...base,
|
|
9
|
+
test: {
|
|
10
|
+
...baseTest,
|
|
11
|
+
include: ["extensions/**/*.test.ts"],
|
|
12
|
+
},
|
|
13
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "denchclaw",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "Fully Managed OpenClaw Framework for managing your CRM, Sales Automation and Outreach agents. The only local productivity tool you need.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/DenchHQ/DenchClaw#readme",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@clack/prompts": "^1.0.1",
|
|
65
|
+
"axios": "^1.13.5",
|
|
65
66
|
"chalk": "^5.6.2",
|
|
66
67
|
"commander": "^14.0.3",
|
|
67
68
|
"gradient-string": "^3.0.0",
|
package/skills/crm/SKILL.md
CHANGED
|
@@ -95,6 +95,51 @@ Never rename partially. If you can't complete all steps, don't start the rename
|
|
|
95
95
|
- **DuckDB SQL errors**: See the "Common DuckDB Pitfalls" section in the **duckdb-operations** child skill. The most frequent causes: unquoted field names with spaces (use `"Full Name"` not `Full Name`), wrong transaction syntax (`BEGIN TRANSACTION` not `BEGIN`), unescaped single quotes, and PIVOT views without the `IN (...)` field list.
|
|
96
96
|
- **"Script not found" on action buttons**: The `.actions/` directory and/or script files were not created. See the **actions** child skill post-creation checklist.
|
|
97
97
|
|
|
98
|
+
## Relation Fields — Link Objects Automatically
|
|
99
|
+
|
|
100
|
+
**Every time you create or modify objects, check whether relation fields should connect them. Do NOT wait for the user to ask — proactively link objects that are obviously related.**
|
|
101
|
+
|
|
102
|
+
Relation fields (`type: "relation"`) let an entry in one object reference an entry in another. They render as a searchable dropdown in the UI and are the backbone of any useful CRM.
|
|
103
|
+
|
|
104
|
+
### Mandatory auto-linking rules
|
|
105
|
+
|
|
106
|
+
When you create an object, scan all existing objects (run `SELECT name FROM objects`) and add relation fields for every obvious link:
|
|
107
|
+
|
|
108
|
+
| If you're creating... | And this object exists... | Auto-create this relation field |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| people / contact | company | "Company" on people → company (`many_to_one`) |
|
|
111
|
+
| lead / prospect | company | "Company" on lead → company (`many_to_one`) |
|
|
112
|
+
| deal / opportunity | people | "Primary Contact" on deal → people (`many_to_one`) |
|
|
113
|
+
| deal / opportunity | company | "Company" on deal → company (`many_to_one`) |
|
|
114
|
+
| task | people | "Assigned Contact" on task → people (`many_to_one`) |
|
|
115
|
+
| task | project / deal | "Related To" on task → parent (`many_to_one`) |
|
|
116
|
+
| case | people or company | "Client" on case → people/company (`many_to_one`) |
|
|
117
|
+
| invoice / payment | company | "Company" on invoice → company (`many_to_one`) |
|
|
118
|
+
| invoice / payment | deal | "Deal" on invoice → deal (`many_to_one`) |
|
|
119
|
+
| property / listing | people | "Agent" on property → people (`many_to_one`) |
|
|
120
|
+
| any child concept | its parent concept | relation to parent (`many_to_one`) |
|
|
121
|
+
|
|
122
|
+
**Use this SQL pattern** (safe — does nothing if the target object doesn't exist):
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
INSERT INTO fields (object_id, name, type, related_object_id, relationship_type, sort_order)
|
|
126
|
+
SELECT
|
|
127
|
+
(SELECT id FROM objects WHERE name = 'people'),
|
|
128
|
+
'Company',
|
|
129
|
+
'relation',
|
|
130
|
+
(SELECT id FROM objects WHERE name = 'company'),
|
|
131
|
+
'many_to_one',
|
|
132
|
+
3
|
|
133
|
+
WHERE EXISTS (SELECT 1 FROM objects WHERE name = 'company')
|
|
134
|
+
ON CONFLICT (object_id, name) DO NOTHING;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**General rule**: If you're creating object B and object A already exists, ask: "Would an entry in B logically belong to or reference an entry in A?" If yes, add a `many_to_one` relation. Use `many_to_many` only when an entry can link to multiple entries (e.g. project → team members).
|
|
138
|
+
|
|
139
|
+
Relation fields **must be created via SQL** — the API does not support the `relation` type.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
98
143
|
## Critical Reminders
|
|
99
144
|
|
|
100
145
|
- Handle the ENTIRE CRM operation from analysis to SQL execution to filesystem projection to summary
|
|
@@ -107,10 +152,11 @@ Never rename partially. If you can't complete all steps, don't start the rename
|
|
|
107
152
|
- Extract ALL data from user messages — don't leave information unused
|
|
108
153
|
- **REPORTS vs DOCUMENTS**: When the user asks for "reports", "analytics", "charts", "graphs", "metrics", "insights", or "breakdown" — use `.report.json` format (see **reports** child skill), NOT markdown. Only use markdown `.md` for SOPs, guides, notes, and prose documents.
|
|
109
154
|
- **INLINE CHART ARTIFACTS**: When answering analytics questions in chat, ALWAYS emit a `report-json` fenced code block so the UI renders interactive charts inline.
|
|
110
|
-
- **NOTES**:
|
|
155
|
+
- **NOTES FIELD vs ENTRY DOCUMENTS**: These are different. `Notes` is a DuckDB `richtext` field stored in `entry_fields`. Entry documents are markdown files linked through the `documents` table. When the user asks for long-form notes, draft emails, writeups, or entry pages, default to entry documents unless they explicitly say "update the Notes field/column".
|
|
156
|
+
- **ENTRY DOCUMENTS**: New entry detail pages should use human-readable filenames (for example `yt-mikemurphy-001.md`, `jane-smith-001.md`, `acme-corp-001.md`) and MUST be registered in DuckDB `documents` with `file_path`, `parent_object_id`, and `entry_id`. Do NOT default to raw `{entry_id}.md` filenames for new docs. See **documents** child skill.
|
|
111
157
|
- **USER FIELDS**: Resolve member name to ID from `workspace_context.yaml` BEFORE inserting
|
|
112
158
|
- **ENUM FIELDS**: Use type "enum" with `enum_values` JSON array
|
|
113
|
-
- **RELATION FIELDS (PROACTIVE
|
|
159
|
+
- **RELATION FIELDS (PROACTIVE — SEE SECTION ABOVE)**: ALWAYS create relation fields when objects are obviously linked — don't wait for the user to ask. See the **"Relation Fields — Link Objects Automatically"** section above for the full table of mandatory auto-linking rules and the SQL pattern.
|
|
114
160
|
- **TAGS FIELDS**: Use type "tags" for free-form string arrays. Value stored as `'["tag1","tag2"]'`
|
|
115
161
|
- **URL FIELDS**: Use type "url" for website addresses and links
|
|
116
162
|
- **FILE FIELDS**: Use type "file" for file attachments (stores file path or URL)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: documents
|
|
3
|
-
description: Document management with markdown files, cross-nesting between documents and objects, and
|
|
3
|
+
description: Document management with markdown files, cross-nesting between documents and objects, and human-readable entry detail pages linked through the documents table.
|
|
4
4
|
metadata: { "openclaw": { "inject": true, "always": true, "emoji": "📄" } }
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -31,34 +31,225 @@ VALUES ('Roadmap', 'map', 'projects/roadmap.md', '<parent_doc_id>', 0);
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Notes Field vs Entry Documents
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
These are **not the same thing**:
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
- **`Notes` field**: a `richtext` value stored in DuckDB `entry_fields`
|
|
39
|
+
- **Entry document**: a standalone `.md` file on disk, linked to the entry through the `documents` table
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
- You do NOT need to create `.md` files for every entry during seeding. Only create them when you want to add detailed notes, descriptions, or documentation for a specific entry.
|
|
42
|
-
- The UI automatically shows an empty markdown editor for entries without a `.md` file, and creates the file on first write.
|
|
43
|
-
- Entry `.md` files are plain markdown (no frontmatter required). The filename must be exactly `{entry_id}.md`.
|
|
41
|
+
When the user says:
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
- "fill in notes for each entry"
|
|
44
|
+
- "write descriptions for all rows"
|
|
45
|
+
- "add detailed writeups"
|
|
46
|
+
- "put the outreach draft into each influencer notes page"
|
|
47
|
+
- "create entry docs"
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
- For entries that need SOPs, playbooks, or documentation attached
|
|
49
|
-
- For key entries like important leads, major projects, or critical tasks
|
|
49
|
+
default to **entry documents** unless they explicitly say "update the Notes column/field".
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
If the user truly means the `Notes` field, they will usually talk about:
|
|
52
|
+
|
|
53
|
+
- a table column
|
|
54
|
+
- richtext field values
|
|
55
|
+
- filtering/sorting by Notes
|
|
56
|
+
- SQL updates to `entry_fields`
|
|
57
|
+
|
|
58
|
+
If they mean entry documents, they are talking about:
|
|
59
|
+
|
|
60
|
+
- markdown pages
|
|
61
|
+
- the entry detail panel
|
|
62
|
+
- prose content, drafts, meeting notes, SOPs, long-form notes
|
|
63
|
+
- files visible under the object in the sidebar
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Entry Detail Pages
|
|
68
|
+
|
|
69
|
+
Each entry in an object can have an optional markdown file that acts as its detail page in the entry panel.
|
|
70
|
+
|
|
71
|
+
**CRITICAL:** New entry documents should use **human-readable filenames**, not raw UUID filenames. The file must also be registered in DuckDB `documents` with `entry_id`, `parent_object_id`, and `file_path`.
|
|
72
|
+
|
|
73
|
+
### Required storage model
|
|
74
|
+
|
|
75
|
+
For every entry document:
|
|
76
|
+
|
|
77
|
+
1. Write a human-readable markdown file inside the object directory
|
|
78
|
+
2. Insert/update a row in `documents` linking the entry to that file
|
|
79
|
+
|
|
80
|
+
```sql
|
|
81
|
+
INSERT INTO documents (title, file_path, parent_object_id, entry_id)
|
|
82
|
+
VALUES (
|
|
83
|
+
'Mike Murphy',
|
|
84
|
+
'marketing/influencer/yt-mikemurphy-001.md',
|
|
85
|
+
(SELECT id FROM objects WHERE name = 'influencer'),
|
|
86
|
+
'yt-mikemurphy-001'
|
|
87
|
+
);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Naming convention (MANDATORY)
|
|
91
|
+
|
|
92
|
+
Use this filename structure for entry documents:
|
|
93
|
+
|
|
94
|
+
`{human_readable_slug}-{sequence}.md`
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
|
|
98
|
+
- `acme-corp-001.md`
|
|
99
|
+
- `jane-smith-001.md`
|
|
100
|
+
- `q2-renewal-deal-001.md`
|
|
101
|
+
|
|
102
|
+
If the entry clearly belongs to a source/domain where a prefix helps, include it:
|
|
103
|
+
|
|
104
|
+
- `yt-mikemurphy-001.md` for YouTube creators
|
|
105
|
+
- `x-somehandle-001.md` for X/Twitter creators
|
|
106
|
+
|
|
107
|
+
### How to choose the slug
|
|
108
|
+
|
|
109
|
+
Use the first strong human-readable identifier available:
|
|
110
|
+
|
|
111
|
+
1. `Document Slug`, `Slug`, or `File Slug` field if it exists
|
|
112
|
+
2. For YouTube creators: extract the handle from `YouTube URL` and prefix `yt-`
|
|
113
|
+
3. Otherwise use the primary text label, such as:
|
|
114
|
+
- `Title`
|
|
115
|
+
- `Channel Name`
|
|
116
|
+
- `Creator Name`
|
|
117
|
+
- `Full Name`
|
|
118
|
+
- `Name`
|
|
119
|
+
- `Company Name`
|
|
120
|
+
- `Deal Name`
|
|
121
|
+
- `Case Number`
|
|
122
|
+
- `Invoice Number`
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
|
|
126
|
+
- `https://www.youtube.com/@MikeMurphy` -> `yt-mikemurphy-001.md`
|
|
127
|
+
- `Creator Name = Jane Smith` -> `jane-smith-001.md`
|
|
128
|
+
- `Company Name = Acme Corp` -> `acme-corp-001.md`
|
|
129
|
+
|
|
130
|
+
### NEVER do these
|
|
131
|
+
|
|
132
|
+
- Do **NOT** default to `{entry_id}.md` for new documents
|
|
133
|
+
- Do **NOT** confuse entry documents with the `Notes` richtext field
|
|
134
|
+
- Do **NOT** create a markdown file without also inserting/updating the `documents` table row
|
|
135
|
+
- Do **NOT** write human-readable files and leave them orphaned from metadata
|
|
136
|
+
|
|
137
|
+
### Backward compatibility
|
|
138
|
+
|
|
139
|
+
Older workspaces may still have legacy `{entry_id}.md` files. Those can continue to work, but **new** entry documents should follow the human-readable naming convention above.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Creating One Entry Document
|
|
144
|
+
|
|
145
|
+
Only create entry `.md` files when the user wants detailed prose for a specific entry or group of entries.
|
|
146
|
+
|
|
147
|
+
Example:
|
|
52
148
|
|
|
53
149
|
```bash
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
150
|
+
cat > {{WORKSPACE_PATH}}/marketing/influencer/yt-mikemurphy-001.md << 'MD'
|
|
151
|
+
# Draft Outreach Email
|
|
152
|
+
|
|
153
|
+
To: hello@mikemurphy.co
|
|
154
|
+
Subject: Partnership idea
|
|
57
155
|
|
|
58
|
-
|
|
156
|
+
Hi Mike,
|
|
59
157
|
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
158
|
+
I loved your AI Handyman breakdowns. DenchClaw is launching a workflow-native AI platform for builders who want serious control over execution, memory, and automation.
|
|
159
|
+
|
|
160
|
+
Would you be open to testing it and discussing a possible sponsorship?
|
|
63
161
|
MD
|
|
64
162
|
```
|
|
163
|
+
|
|
164
|
+
Then register it:
|
|
165
|
+
|
|
166
|
+
```sql
|
|
167
|
+
INSERT INTO documents (title, file_path, parent_object_id, entry_id)
|
|
168
|
+
VALUES (
|
|
169
|
+
'Mike Murphy',
|
|
170
|
+
'marketing/influencer/yt-mikemurphy-001.md',
|
|
171
|
+
(SELECT id FROM objects WHERE name = 'influencer'),
|
|
172
|
+
'yt-mikemurphy-001'
|
|
173
|
+
)
|
|
174
|
+
ON CONFLICT (file_path) DO UPDATE
|
|
175
|
+
SET title = excluded.title,
|
|
176
|
+
parent_object_id = excluded.parent_object_id,
|
|
177
|
+
entry_id = excluded.entry_id,
|
|
178
|
+
updated_at = now();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Batch Creating Entry Documents
|
|
184
|
+
|
|
185
|
+
When the user asks for docs for many entries, do **not** update the `Notes` field in SQL. Create markdown files plus `documents` rows.
|
|
186
|
+
|
|
187
|
+
### Workflow
|
|
188
|
+
|
|
189
|
+
1. Query entries and the fields needed to build filenames/titles
|
|
190
|
+
2. Derive a human-readable filename for each entry
|
|
191
|
+
3. Write one `.md` file per entry under the object directory
|
|
192
|
+
4. Insert/update one `documents` row per file
|
|
193
|
+
|
|
194
|
+
### Example: create docs for every influencer
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
duckdb {{WORKSPACE_PATH}}/workspace.duckdb -json "
|
|
198
|
+
SELECT
|
|
199
|
+
entry_id,
|
|
200
|
+
\"Creator Name\",
|
|
201
|
+
\"Channel Name\",
|
|
202
|
+
\"YouTube URL\"
|
|
203
|
+
FROM v_influencer
|
|
204
|
+
ORDER BY \"Creator Name\"
|
|
205
|
+
"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Then for each row:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Example row:
|
|
212
|
+
# entry_id = yt-mikemurphy-001
|
|
213
|
+
# youtube url = https://www.youtube.com/@MikeMurphy
|
|
214
|
+
|
|
215
|
+
cat > {{WORKSPACE_PATH}}/marketing/influencer/yt-mikemurphy-001.md << 'MD'
|
|
216
|
+
# Influencer Notes
|
|
217
|
+
|
|
218
|
+
## Outreach draft
|
|
219
|
+
|
|
220
|
+
...
|
|
221
|
+
MD
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Register each file:
|
|
225
|
+
|
|
226
|
+
```sql
|
|
227
|
+
INSERT INTO documents (title, file_path, parent_object_id, entry_id)
|
|
228
|
+
VALUES (
|
|
229
|
+
'Mike Murphy',
|
|
230
|
+
'marketing/influencer/yt-mikemurphy-001.md',
|
|
231
|
+
(SELECT id FROM objects WHERE name = 'influencer'),
|
|
232
|
+
'yt-mikemurphy-001'
|
|
233
|
+
)
|
|
234
|
+
ON CONFLICT (file_path) DO UPDATE
|
|
235
|
+
SET title = excluded.title,
|
|
236
|
+
parent_object_id = excluded.parent_object_id,
|
|
237
|
+
entry_id = excluded.entry_id,
|
|
238
|
+
updated_at = now();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Standalone Documents vs Entry Documents
|
|
244
|
+
|
|
245
|
+
Not every markdown file under an object directory is automatically an entry document.
|
|
246
|
+
|
|
247
|
+
- **Entry document**: has a corresponding `documents` row with `entry_id` set
|
|
248
|
+
- **Standalone document under an object**: has no `entry_id` link; it is just a nested document in that folder
|
|
249
|
+
|
|
250
|
+
Examples:
|
|
251
|
+
|
|
252
|
+
- `marketing/influencer/yt-mikemurphy-001.md` with `documents.entry_id = 'yt-mikemurphy-001'` -> entry document
|
|
253
|
+
- `marketing/influencer/outreach-playbook.md` with no `entry_id` -> standalone object-level document
|
|
254
|
+
|
|
255
|
+
If a markdown file is meant to be the entry page, always register it in `documents`.
|
|
@@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS documents (
|
|
|
96
96
|
file_path VARCHAR NOT NULL UNIQUE,
|
|
97
97
|
parent_id VARCHAR REFERENCES documents(id),
|
|
98
98
|
parent_object_id VARCHAR REFERENCES objects(id),
|
|
99
|
+
entry_id VARCHAR REFERENCES entries(id),
|
|
99
100
|
sort_order INTEGER DEFAULT 0,
|
|
100
101
|
is_published BOOLEAN DEFAULT false,
|
|
101
102
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
@@ -122,6 +123,8 @@ CREATE TABLE IF NOT EXISTS action_runs (
|
|
|
122
123
|
|
|
123
124
|
All ID columns default to `gen_random_uuid()::VARCHAR`. You can also use `nanoid32()` if the macro is loaded. When inserting manually, always generate an ID — never use a placeholder.
|
|
124
125
|
|
|
126
|
+
For entry detail markdown pages, `documents.entry_id` links a document row to a specific CRM entry while `file_path` stores the human-readable markdown path (for example `marketing/influencer/yt-mikemurphy-001.md`).
|
|
127
|
+
|
|
125
128
|
---
|
|
126
129
|
|
|
127
130
|
## Auto-Generated PIVOT Views
|