@agentprojectcontext/apx 1.58.0 → 1.60.0
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/package.json +1 -1
- package/src/core/agent/skills/index.js +9 -0
- package/src/core/agent/skills/inspector.js +7 -2
- package/src/core/agent/skills/policy.js +128 -0
- package/src/core/agent/super-agent.js +8 -1
- package/src/core/agent/tools/handlers/_asana.js +34 -0
- package/src/core/agent/tools/handlers/asana-create-task.js +38 -0
- package/src/core/agent/tools/handlers/asana-list-projects.js +19 -0
- package/src/core/agent/tools/handlers/asana-list-tasks.js +27 -0
- package/src/core/agent/tools/handlers/asana-update-task.js +32 -0
- package/src/core/agent/tools/handlers/list-skills.js +6 -3
- package/src/core/agent/tools/handlers/load-skill.js +9 -2
- package/src/core/agent/tools/names.js +10 -0
- package/src/core/agent/tools/registry.js +11 -0
- package/src/core/integrations/catalog.js +66 -0
- package/src/core/integrations/index.js +10 -0
- package/src/core/integrations/plugins/asana.js +231 -0
- package/src/core/integrations/sources.js +56 -0
- package/src/core/integrations/store.js +118 -0
- package/src/host/daemon/api/integrations.js +191 -0
- package/src/host/daemon/api/skills.js +301 -17
- package/src/host/daemon/api.js +2 -0
- package/src/interfaces/cli/commands/skills.js +3 -2
- package/src/interfaces/web/dist/assets/index-DFNV6BWh.js +761 -0
- package/src/interfaces/web/dist/assets/{index-DPAuXATr.js.map → index-DFNV6BWh.js.map} +1 -1
- package/src/interfaces/web/dist/assets/index-HU-Wt2l9.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/components/integrations/AsanaPlugin.tsx +275 -0
- package/src/interfaces/web/src/components/integrations/ComingSoonPlugin.tsx +44 -0
- package/src/interfaces/web/src/components/integrations/PluginCard.tsx +61 -0
- package/src/interfaces/web/src/components/integrations/PluginToolsSection.tsx +39 -0
- package/src/interfaces/web/src/components/settings/SkillsManager.tsx +465 -0
- package/src/interfaces/web/src/components/settings/SkillsSettings.tsx +49 -0
- package/src/interfaces/web/src/i18n/en.ts +67 -0
- package/src/interfaces/web/src/i18n/es.ts +67 -0
- package/src/interfaces/web/src/lib/api/integrations.ts +106 -0
- package/src/interfaces/web/src/lib/api/skills.ts +79 -8
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +12 -4
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +3 -3
- package/src/interfaces/web/src/screens/project/IntegrationsTab.tsx +146 -0
- package/src/interfaces/web/src/screens/project/SkillsTab.tsx +13 -0
- package/src/interfaces/web/dist/assets/index-Cl0WXtxF.css +0 -1
- package/src/interfaces/web/dist/assets/index-DPAuXATr.js +0 -705
package/package.json
CHANGED
|
@@ -3,6 +3,15 @@ export { condenseSkillDescription, buildSkillsHintBlock } from "./catalog.js";
|
|
|
3
3
|
export { tryResolveSkillCommand } from "./trigger.js";
|
|
4
4
|
export { suggestSkillForPrompt, clearSkillVectorCache } from "./rag.js";
|
|
5
5
|
export { listSkills, loadSkill, SKILL_LOCATIONS } from "./loader.js";
|
|
6
|
+
export {
|
|
7
|
+
isPrivateSkill,
|
|
8
|
+
isSkillEnabled,
|
|
9
|
+
filterEnabledSkills,
|
|
10
|
+
annotateSkills,
|
|
11
|
+
setSkillEnabled,
|
|
12
|
+
resolveScopeKey,
|
|
13
|
+
DEFAULT_SCOPE,
|
|
14
|
+
} from "./policy.js";
|
|
6
15
|
export {
|
|
7
16
|
inspectPromptForSkills,
|
|
8
17
|
isInspectorEnabled,
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { embedOne, cosineSim } from "#core/memory/embeddings.js";
|
|
27
27
|
import { listSkills, loadSkill } from "./loader.js";
|
|
28
|
+
import { filterEnabledSkills, isSkillEnabled } from "./policy.js";
|
|
28
29
|
import { readIndex, backgroundRefreshIfStale } from "./index-store.js";
|
|
29
30
|
|
|
30
31
|
// Defaults — exported so the CLI/web can render them.
|
|
@@ -187,7 +188,8 @@ export async function inspectPromptForSkills({ prompt, projectPath, globalConfig
|
|
|
187
188
|
};
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
const scored = scoreAgainstIndex(probe.vector, items)
|
|
191
|
+
const scored = scoreAgainstIndex(probe.vector, items).filter((s) =>
|
|
192
|
+
isSkillEnabled(s, { config: globalConfig, projectPath }));
|
|
191
193
|
return await pickAndRender({ scored, projectPath, probe, cfg });
|
|
192
194
|
}
|
|
193
195
|
|
|
@@ -196,7 +198,10 @@ export async function inspectPromptForSkills({ prompt, projectPath, globalConfig
|
|
|
196
198
|
// ---------------------------------------------------------------------------
|
|
197
199
|
|
|
198
200
|
async function inspectFromLive({ text, projectPath, cfg, globalConfig, embedOpts }) {
|
|
199
|
-
const skills = listSkills({ projectPath })
|
|
201
|
+
const skills = filterEnabledSkills(listSkills({ projectPath }), {
|
|
202
|
+
config: globalConfig,
|
|
203
|
+
projectPath,
|
|
204
|
+
});
|
|
200
205
|
if (!skills.length) {
|
|
201
206
|
return { contextNote: "", trace: { enabled: true, reason: "no_skills" } };
|
|
202
207
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Skills enable/disable policy — scope-aware gating shared by every consumer.
|
|
2
|
+
//
|
|
3
|
+
// A skill can be turned off per scope. A "scope" is either the super-agent /
|
|
4
|
+
// no-project baseline ("default") or a specific project (keyed by its absolute
|
|
5
|
+
// path). Config lives at:
|
|
6
|
+
//
|
|
7
|
+
// config.skills.policy["default"] = { "<slug>": true|false, ... }
|
|
8
|
+
// config.skills.policy["<projectPath>"] = { "<slug>": true|false, ... }
|
|
9
|
+
//
|
|
10
|
+
// A boolean under a scope is an explicit override; an absent slug inherits.
|
|
11
|
+
// Empty/missing policy = every skill enabled (the pre-feature behavior), so this
|
|
12
|
+
// is fully backward compatible.
|
|
13
|
+
//
|
|
14
|
+
// PRIVATE skills (source "builtin") are APX's own shipped skills. They are
|
|
15
|
+
// always active and can never be disabled or deleted — the UI shows them locked.
|
|
16
|
+
//
|
|
17
|
+
// Effective enabled(skill, projectPath):
|
|
18
|
+
// 1. builtin source → true (private, locked)
|
|
19
|
+
// 2. project scope explicit value → that value
|
|
20
|
+
// 3. "default" scope explicit value→ that value
|
|
21
|
+
// 4. otherwise → true
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_SCOPE = "default";
|
|
24
|
+
|
|
25
|
+
// Sources whose skills ship with apx and are always active.
|
|
26
|
+
const PRIVATE_SOURCES = new Set(["builtin"]);
|
|
27
|
+
|
|
28
|
+
/** True for APX's own built-in skills — always active, never disableable. */
|
|
29
|
+
export function isPrivateSkill(skill) {
|
|
30
|
+
return PRIVATE_SOURCES.has(skill?.source);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Normalize a project path (or nothing) into a policy scope key. */
|
|
34
|
+
export function resolveScopeKey(projectPath) {
|
|
35
|
+
const p = typeof projectPath === "string" ? projectPath.trim() : "";
|
|
36
|
+
return p || DEFAULT_SCOPE;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function policyMap(config) {
|
|
40
|
+
const p = config?.skills?.policy;
|
|
41
|
+
return p && typeof p === "object" ? p : {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function scopeOverride(config, scopeKey, slug) {
|
|
45
|
+
const scope = policyMap(config)[scopeKey];
|
|
46
|
+
if (!scope || typeof scope !== "object") return undefined;
|
|
47
|
+
const v = scope[slug];
|
|
48
|
+
return typeof v === "boolean" ? v : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve whether a skill is enabled for the given scope.
|
|
53
|
+
* @param {{slug:string, source?:string}} skill
|
|
54
|
+
* @param {{config?:object, projectPath?:string}} ctx
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function isSkillEnabled(skill, { config, projectPath } = {}) {
|
|
58
|
+
if (isPrivateSkill(skill)) return true;
|
|
59
|
+
const slug = skill?.slug;
|
|
60
|
+
if (!slug) return true;
|
|
61
|
+
|
|
62
|
+
const scopeKey = resolveScopeKey(projectPath);
|
|
63
|
+
if (scopeKey !== DEFAULT_SCOPE) {
|
|
64
|
+
const own = scopeOverride(config, scopeKey, slug);
|
|
65
|
+
if (own !== undefined) return own;
|
|
66
|
+
}
|
|
67
|
+
const base = scopeOverride(config, DEFAULT_SCOPE, slug);
|
|
68
|
+
if (base !== undefined) return base;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Keep only the skills enabled for the given scope. */
|
|
73
|
+
export function filterEnabledSkills(skills, ctx = {}) {
|
|
74
|
+
if (!Array.isArray(skills)) return [];
|
|
75
|
+
return skills.filter((s) => isSkillEnabled(s, ctx));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Annotate skills for a UI client: adds `enabled`, `private`, and `overridden`
|
|
80
|
+
* (whether *this* scope holds an explicit override, ignoring inheritance).
|
|
81
|
+
*/
|
|
82
|
+
export function annotateSkills(skills, { config, projectPath } = {}) {
|
|
83
|
+
if (!Array.isArray(skills)) return [];
|
|
84
|
+
const scopeKey = resolveScopeKey(projectPath);
|
|
85
|
+
return skills.map((s) => {
|
|
86
|
+
const priv = isPrivateSkill(s);
|
|
87
|
+
const own = priv ? undefined : scopeOverride(config, scopeKey, s.slug);
|
|
88
|
+
return {
|
|
89
|
+
...s,
|
|
90
|
+
private: priv,
|
|
91
|
+
enabled: isSkillEnabled(s, { config, projectPath }),
|
|
92
|
+
overridden: own !== undefined,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Set (or clear) a skill's enabled override for a scope. Mutates and returns the
|
|
99
|
+
* config object. `enabled === null|undefined` clears the override (back to
|
|
100
|
+
* inherit). Private/builtin skills cannot be overridden.
|
|
101
|
+
*
|
|
102
|
+
* @param {object} config the global config (mutated in place)
|
|
103
|
+
* @param {object} args
|
|
104
|
+
* @param {string} args.slug
|
|
105
|
+
* @param {boolean|null} args.enabled
|
|
106
|
+
* @param {string=} args.scope scope key ("default" or a project path)
|
|
107
|
+
* @param {string=} args.projectPath alternative to scope; normalized to a key
|
|
108
|
+
*/
|
|
109
|
+
export function setSkillEnabled(config, { slug, enabled, scope, projectPath } = {}) {
|
|
110
|
+
if (!slug) throw new Error("setSkillEnabled: slug required");
|
|
111
|
+
const scopeKey = scope ? resolveScopeKey(scope) : resolveScopeKey(projectPath);
|
|
112
|
+
|
|
113
|
+
config.skills = config.skills || {};
|
|
114
|
+
config.skills.policy = config.skills.policy || {};
|
|
115
|
+
const map = config.skills.policy;
|
|
116
|
+
|
|
117
|
+
if (enabled === null || enabled === undefined) {
|
|
118
|
+
if (map[scopeKey]) {
|
|
119
|
+
delete map[scopeKey][slug];
|
|
120
|
+
if (Object.keys(map[scopeKey]).length === 0) delete map[scopeKey];
|
|
121
|
+
}
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
map[scopeKey] = map[scopeKey] || {};
|
|
126
|
+
map[scopeKey][slug] = !!enabled;
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Super-agent: daemon-level action agent for Telegram, TUI, desktop, routines.
|
|
2
2
|
import { createToolSession, buildLazyToolsBlock, makeToolHandlers } from "#core/agent/tools/registry.js";
|
|
3
3
|
import { listSkills } from "#core/agent/skills/loader.js";
|
|
4
|
+
import { filterEnabledSkills } from "#core/agent/skills/policy.js";
|
|
4
5
|
import {
|
|
5
6
|
runAgent,
|
|
6
7
|
buildSuperAgentSystem,
|
|
@@ -97,10 +98,16 @@ export async function runSuperAgent({
|
|
|
97
98
|
// noTools callers (summarize/ask) get no session — text only.
|
|
98
99
|
const toolSession = noTools ? null : createToolSession(channel, { allowedTools });
|
|
99
100
|
|
|
101
|
+
// Scope the catalog hint to the skills enabled for this project (or the
|
|
102
|
+
// super-agent baseline when no project). Built-in/private skills always pass.
|
|
103
|
+
const projectPath = channelMeta?.projectPath;
|
|
104
|
+
const scopedListSkills = (opts = {}) =>
|
|
105
|
+
filterEnabledSkills(listSkills(opts), { config: globalConfig, projectPath });
|
|
106
|
+
|
|
100
107
|
const system = buildSuperAgentSystem({
|
|
101
108
|
globalConfig,
|
|
102
109
|
projects,
|
|
103
|
-
listSkills,
|
|
110
|
+
listSkills: scopedListSkills,
|
|
104
111
|
contextNote,
|
|
105
112
|
channel,
|
|
106
113
|
channelMeta,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Shared helpers for the Asana agent tools (asana-*.js). Kept in an underscore
|
|
2
|
+
// file — like _git.js — so it holds no tool `name:` of its own. Each tool file
|
|
3
|
+
// stays a thin adapter: resolve the project's effective Asana integration (its
|
|
4
|
+
// own record wins over the default project's — see resolveIntegration), then
|
|
5
|
+
// call the pure REST client in core/integrations/plugins/asana.js.
|
|
6
|
+
import { resolveProject } from "../helpers.js";
|
|
7
|
+
import { resolveIntegration } from "#core/integrations/index.js";
|
|
8
|
+
|
|
9
|
+
// Resolve the active Asana token + config for a project, or throw a message the
|
|
10
|
+
// model can act on (tell the user to connect Asana in the web panel).
|
|
11
|
+
export function resolveAsana(projects, project) {
|
|
12
|
+
const p = resolveProject(projects, project);
|
|
13
|
+
const resolved = resolveIntegration({ projectStorage: p.storagePath, slug: "asana" });
|
|
14
|
+
if (!resolved) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Asana is not connected for this project. Ask the user to connect it in the web panel → Integrations → Plugins → Asana.",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const config = resolved.record.config || {};
|
|
20
|
+
const token = config.personal_access_token;
|
|
21
|
+
if (!token) throw new Error("Asana integration has no token configured");
|
|
22
|
+
return { token, config, scope: resolved.scope };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function requireWorkspace(config) {
|
|
26
|
+
const gid = config.workspace_gid;
|
|
27
|
+
if (!gid) throw new Error("No Asana workspace selected. Configure it in the web panel first.");
|
|
28
|
+
return gid;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The optional `project` arg every Asana tool accepts (defaults to current).
|
|
32
|
+
export const PROJECT_ARG = {
|
|
33
|
+
project: { type: "string", description: "APX project id/name (optional; defaults to current)" },
|
|
34
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as asana from "#core/integrations/plugins/asana.js";
|
|
2
|
+
import { resolveAsana, requireWorkspace, PROJECT_ARG } from "./_asana.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "asana_create_task",
|
|
6
|
+
category: "integrations",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "asana_create_task",
|
|
11
|
+
description: "Create an Asana task in the connected workspace.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
name: { type: "string", description: "Task title" },
|
|
16
|
+
notes: { type: "string", description: "Task description/notes" },
|
|
17
|
+
project_gid: { type: "string", description: "Optional Asana project gid to add the task to" },
|
|
18
|
+
assignee: { type: "string", description: "Optional assignee (user gid or 'me')" },
|
|
19
|
+
due_on: { type: "string", description: "Optional due date (YYYY-MM-DD)" },
|
|
20
|
+
...PROJECT_ARG,
|
|
21
|
+
},
|
|
22
|
+
required: ["name"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
makeHandler: ({ projects }) => async ({ project, name, notes, project_gid, assignee, due_on } = {}) => {
|
|
27
|
+
const { token, config } = resolveAsana(projects, project);
|
|
28
|
+
const task = await asana.createTask(token, {
|
|
29
|
+
workspaceGid: requireWorkspace(config),
|
|
30
|
+
name,
|
|
31
|
+
notes,
|
|
32
|
+
projectGid: project_gid,
|
|
33
|
+
assignee,
|
|
34
|
+
dueOn: due_on,
|
|
35
|
+
});
|
|
36
|
+
return { task };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as asana from "#core/integrations/plugins/asana.js";
|
|
2
|
+
import { resolveAsana, requireWorkspace, PROJECT_ARG } from "./_asana.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "asana_list_projects",
|
|
6
|
+
category: "integrations",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "asana_list_projects",
|
|
11
|
+
description: "List Asana projects in the connected workspace.",
|
|
12
|
+
parameters: { type: "object", properties: { ...PROJECT_ARG } },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
makeHandler: ({ projects }) => async ({ project } = {}) => {
|
|
16
|
+
const { token, config } = resolveAsana(projects, project);
|
|
17
|
+
return { projects: await asana.listProjects(token, requireWorkspace(config)) };
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as asana from "#core/integrations/plugins/asana.js";
|
|
2
|
+
import { resolveAsana, PROJECT_ARG } from "./_asana.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "asana_list_tasks",
|
|
6
|
+
category: "integrations",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "asana_list_tasks",
|
|
11
|
+
description: "List tasks in an Asana project.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
project_gid: { type: "string", description: "Asana project gid (from asana_list_projects)" },
|
|
16
|
+
completed: { type: "boolean", description: "Include completed tasks (default false)" },
|
|
17
|
+
...PROJECT_ARG,
|
|
18
|
+
},
|
|
19
|
+
required: ["project_gid"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
makeHandler: ({ projects }) => async ({ project, project_gid, completed = false } = {}) => {
|
|
24
|
+
const { token } = resolveAsana(projects, project);
|
|
25
|
+
return { tasks: await asana.listTasks(token, project_gid, completed) };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as asana from "#core/integrations/plugins/asana.js";
|
|
2
|
+
import { resolveAsana, PROJECT_ARG } from "./_asana.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "asana_update_task",
|
|
6
|
+
category: "integrations",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "asana_update_task",
|
|
11
|
+
description: "Update an Asana task's fields (rename, complete, reschedule, reassign).",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
task_gid: { type: "string", description: "Asana task gid" },
|
|
16
|
+
name: { type: "string" },
|
|
17
|
+
notes: { type: "string" },
|
|
18
|
+
completed: { type: "boolean" },
|
|
19
|
+
due_on: { type: "string", description: "YYYY-MM-DD" },
|
|
20
|
+
assignee: { type: "string" },
|
|
21
|
+
...PROJECT_ARG,
|
|
22
|
+
},
|
|
23
|
+
required: ["task_gid"],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
makeHandler: ({ projects }) => async ({ project, task_gid, name, notes, completed, due_on, assignee } = {}) => {
|
|
28
|
+
const { token } = resolveAsana(projects, project);
|
|
29
|
+
const task = await asana.updateTask(token, task_gid, { name, notes, completed, dueOn: due_on, assignee });
|
|
30
|
+
return { task };
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { listSkills, SKILL_LOCATIONS } from "#core/agent/skills/loader.js";
|
|
2
|
-
import { condenseSkillDescription } from "#core/agent/skills/index.js";
|
|
2
|
+
import { condenseSkillDescription, filterEnabledSkills } from "#core/agent/skills/index.js";
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
name: "list_skills",
|
|
@@ -20,8 +20,11 @@ export default {
|
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
-
makeHandler: () => ({ project_path } = {}) => {
|
|
24
|
-
const skills =
|
|
23
|
+
makeHandler: (ctx = {}) => ({ project_path } = {}) => {
|
|
24
|
+
const skills = filterEnabledSkills(
|
|
25
|
+
listSkills({ projectPath: project_path }),
|
|
26
|
+
{ config: ctx.globalConfig, projectPath: project_path },
|
|
27
|
+
);
|
|
25
28
|
return {
|
|
26
29
|
ok: true,
|
|
27
30
|
count: skills.length,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadSkill } from "#core/agent/skills/loader.js";
|
|
2
|
+
import { isSkillEnabled } from "#core/agent/skills/policy.js";
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
name: "load_skill",
|
|
@@ -24,8 +25,14 @@ export default {
|
|
|
24
25
|
},
|
|
25
26
|
},
|
|
26
27
|
},
|
|
27
|
-
makeHandler: () => ({ slug, project_path } = {}) => {
|
|
28
|
+
makeHandler: (ctx = {}) => ({ slug, project_path } = {}) => {
|
|
28
29
|
if (!slug) throw new Error("load_skill: slug required");
|
|
29
|
-
|
|
30
|
+
const skill = loadSkill(slug, { projectPath: project_path });
|
|
31
|
+
if (!isSkillEnabled(skill, { config: ctx.globalConfig, projectPath: project_path })) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`skill "${slug}" is disabled for this scope. Enable it in Settings → Skills, or pick another.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return skill;
|
|
30
37
|
},
|
|
31
38
|
};
|
|
@@ -50,6 +50,12 @@ export const TOOLS = Object.freeze({
|
|
|
50
50
|
CALL_MCP: "call_mcp",
|
|
51
51
|
CALL_RUNTIME: "call_runtime",
|
|
52
52
|
|
|
53
|
+
// Integrations — Asana plugin (see core/integrations/plugins/asana.js)
|
|
54
|
+
ASANA_LIST_PROJECTS: "asana_list_projects",
|
|
55
|
+
ASANA_LIST_TASKS: "asana_list_tasks",
|
|
56
|
+
ASANA_CREATE_TASK: "asana_create_task",
|
|
57
|
+
ASANA_UPDATE_TASK: "asana_update_task",
|
|
58
|
+
|
|
53
59
|
// Side-effects
|
|
54
60
|
SEND_TELEGRAM: "send_telegram",
|
|
55
61
|
SET_IDENTITY: "set_identity",
|
|
@@ -94,6 +100,10 @@ export const NATIVE_TOOL_NAMES = new Set([
|
|
|
94
100
|
TOOLS.CALL_AGENT,
|
|
95
101
|
TOOLS.CALL_MCP,
|
|
96
102
|
TOOLS.CALL_RUNTIME,
|
|
103
|
+
TOOLS.ASANA_LIST_PROJECTS,
|
|
104
|
+
TOOLS.ASANA_LIST_TASKS,
|
|
105
|
+
TOOLS.ASANA_CREATE_TASK,
|
|
106
|
+
TOOLS.ASANA_UPDATE_TASK,
|
|
97
107
|
TOOLS.SEND_TELEGRAM,
|
|
98
108
|
TOOLS.SET_IDENTITY,
|
|
99
109
|
TOOLS.SET_PERMISSION_MODE,
|
|
@@ -33,6 +33,10 @@ import gitStatus from "./handlers/git-status.js";
|
|
|
33
33
|
import gitDiff from "./handlers/git-diff.js";
|
|
34
34
|
import gitLog from "./handlers/git-log.js";
|
|
35
35
|
import gitShow from "./handlers/git-show.js";
|
|
36
|
+
import asanaListProjects from "./handlers/asana-list-projects.js";
|
|
37
|
+
import asanaListTasks from "./handlers/asana-list-tasks.js";
|
|
38
|
+
import asanaCreateTask from "./handlers/asana-create-task.js";
|
|
39
|
+
import asanaUpdateTask from "./handlers/asana-update-task.js";
|
|
36
40
|
import { createPermissionGuard } from "./helpers.js";
|
|
37
41
|
import { buildBridgedTools, DEFAULT_CATEGORIES } from "./registry-bridge.js";
|
|
38
42
|
import { TOOLS, CODE_CHANNEL_TOOLS } from "./names.js";
|
|
@@ -74,6 +78,13 @@ const NATIVE_TOOLS = [
|
|
|
74
78
|
gitDiff,
|
|
75
79
|
gitLog,
|
|
76
80
|
gitShow,
|
|
81
|
+
// Integration plugin tools (Asana). Each carries its own `category`, so it
|
|
82
|
+
// lands in the "integrations" group of the discover_tools catalog and stays
|
|
83
|
+
// lazy on chat channels until activated.
|
|
84
|
+
asanaListProjects,
|
|
85
|
+
asanaListTasks,
|
|
86
|
+
asanaCreateTask,
|
|
87
|
+
asanaUpdateTask,
|
|
77
88
|
];
|
|
78
89
|
|
|
79
90
|
// Registry-backed bridges. Categories can be overridden per-process via env
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Plugin catalog — the source of truth for which integration plugins exist and
|
|
2
|
+
// which are wired end-to-end. The daemon API + web "Plugins" tab read this.
|
|
3
|
+
//
|
|
4
|
+
// A plugin is "implemented" when it has a service module in ./plugins/ that
|
|
5
|
+
// satisfies the lifecycle contract (configure/validate/status/deactivate). The
|
|
6
|
+
// rest are declared here with `coming_soon: true` so the Integrations page shows
|
|
7
|
+
// the full roster (matching PandaProject) while remaining honest about what is
|
|
8
|
+
// actually connectable. Adding a new plugin = drop a module in ./plugins/ and
|
|
9
|
+
// register it in PLUGIN_SERVICES — no API or route changes (open/closed).
|
|
10
|
+
import { asanaPlugin } from "./plugins/asana.js";
|
|
11
|
+
|
|
12
|
+
// slug -> live plugin service (must implement the lifecycle contract).
|
|
13
|
+
export const PLUGIN_SERVICES = Object.freeze({
|
|
14
|
+
asana: asanaPlugin,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Static descriptors for the catalog UI. Implemented plugins derive their
|
|
18
|
+
// metadata from the service module; coming-soon ones are declared inline.
|
|
19
|
+
// `coming_soon` entries in PandaProject depend on domain/channel infrastructure
|
|
20
|
+
// (GitHub outputs↔repos, a WhatsApp-Web bridge, Telegram-voice whisper) that
|
|
21
|
+
// APX models differently — they are placeholders until ported natively.
|
|
22
|
+
const COMING_SOON = [
|
|
23
|
+
{
|
|
24
|
+
slug: "github",
|
|
25
|
+
name: "GitHub",
|
|
26
|
+
type: "source_control",
|
|
27
|
+
description: "Vinculá repos, issues y PRs para que los agentes trabajen sobre tu código",
|
|
28
|
+
auth: "token",
|
|
29
|
+
coming_soon: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
slug: "whatsapp",
|
|
33
|
+
name: "WhatsApp",
|
|
34
|
+
type: "channel",
|
|
35
|
+
description: "Conectá WhatsApp Web al orquestador — responde mensajes automáticamente",
|
|
36
|
+
auth: "qr",
|
|
37
|
+
coming_soon: true,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
slug: "local-transcription",
|
|
41
|
+
name: "Transcripción Local",
|
|
42
|
+
type: "transcription",
|
|
43
|
+
description: "Transcripción de audio con faster-whisper local — sin depender de una API externa",
|
|
44
|
+
auth: "none",
|
|
45
|
+
coming_soon: true,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// The full catalog: implemented plugins first, then coming-soon.
|
|
50
|
+
export function listCatalog() {
|
|
51
|
+
const implemented = Object.values(PLUGIN_SERVICES).map((p) => ({
|
|
52
|
+
slug: p.slug,
|
|
53
|
+
name: p.name,
|
|
54
|
+
type: p.type,
|
|
55
|
+
description: p.description,
|
|
56
|
+
auth: p.auth,
|
|
57
|
+
tools: p.tools || [],
|
|
58
|
+
coming_soon: false,
|
|
59
|
+
}));
|
|
60
|
+
return [...implemented, ...COMING_SOON];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Resolve a live plugin service by slug (or null when not implemented).
|
|
64
|
+
export function getPluginService(slug) {
|
|
65
|
+
return PLUGIN_SERVICES[slug] || null;
|
|
66
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public surface of the integrations core module. Daemon + agent tools import
|
|
2
|
+
// from here.
|
|
3
|
+
export {
|
|
4
|
+
integrationsPath,
|
|
5
|
+
defaultIntegrationsStorage,
|
|
6
|
+
readIntegrations,
|
|
7
|
+
writeIntegrations,
|
|
8
|
+
} from "./sources.js";
|
|
9
|
+
export { IntegrationStore, resolveIntegration, redactRecord } from "./store.js";
|
|
10
|
+
export { listCatalog, getPluginService, PLUGIN_SERVICES } from "./catalog.js";
|