@redaksjon/protokoll 1.0.31-dev.20260317224752.adeddfd → 1.0.31-dev.20260320152728.3166fe3
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/dist/engineLogging.js +297 -48
- package/dist/engineLogging.js.map +1 -1
- package/dist/mcp/server-hono.js +12 -0
- package/dist/mcp/server-hono.js.map +1 -1
- package/package.json +1 -1
package/dist/engineLogging.js
CHANGED
|
@@ -208,7 +208,7 @@ function isProtokolUri(uri) {
|
|
|
208
208
|
return uri.startsWith(`${SCHEME}://`);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
const VERSION = "1.0.31-dev.
|
|
211
|
+
const VERSION = "1.0.31-dev.20260320152728.3166fe3 (working/3166fe3 2026-03-20 08:26:22 -0700) linux arm64 v24.14.0";
|
|
212
212
|
const PROGRAM_NAME = "protokoll";
|
|
213
213
|
const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = "YYYY-M-D-HHmmss";
|
|
214
214
|
const DEFAULT_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm", "qta"];
|
|
@@ -2961,7 +2961,47 @@ async function readTranscriptsListResource(options) {
|
|
|
2961
2961
|
};
|
|
2962
2962
|
}
|
|
2963
2963
|
|
|
2964
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2965
|
+
function resolveCanonicalEntityId$1(id) {
|
|
2966
|
+
const t = id.trim();
|
|
2967
|
+
const colon = t.indexOf(":");
|
|
2968
|
+
if (colon === -1) {
|
|
2969
|
+
return t;
|
|
2970
|
+
}
|
|
2971
|
+
const rest = t.slice(colon + 1).trim();
|
|
2972
|
+
if (UUID_RE.test(rest)) {
|
|
2973
|
+
return rest;
|
|
2974
|
+
}
|
|
2975
|
+
return t;
|
|
2976
|
+
}
|
|
2977
|
+
function entityIdLookupOrder(id) {
|
|
2978
|
+
const t = id.trim();
|
|
2979
|
+
const canon = resolveCanonicalEntityId$1(t);
|
|
2980
|
+
return canon === t ? [t] : [t, canon];
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2964
2983
|
const logger$5 = Logging.getLogger("@redaksjon/protokoll-mcp").get("entity-resources");
|
|
2984
|
+
const PROJECT_PLAN_ARRAY_KEYS$1 = ["related_plans", "plans", "riotplan_plans"];
|
|
2985
|
+
function stripProjectPlanArraysForResource(entity) {
|
|
2986
|
+
const base = entity && typeof entity === "object" && !Array.isArray(entity) ? { ...entity } : {};
|
|
2987
|
+
let totalRows = 0;
|
|
2988
|
+
for (const k of PROJECT_PLAN_ARRAY_KEYS$1) {
|
|
2989
|
+
const v = base[k];
|
|
2990
|
+
if (Array.isArray(v)) {
|
|
2991
|
+
totalRows += v.length;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
for (const k of PROJECT_PLAN_ARRAY_KEYS$1) {
|
|
2995
|
+
const v = base[k];
|
|
2996
|
+
if (Array.isArray(v) && v.length > 0) {
|
|
2997
|
+
delete base[k];
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
if (totalRows > 0) {
|
|
3001
|
+
base.related_plans_total = totalRows;
|
|
3002
|
+
}
|
|
3003
|
+
return base;
|
|
3004
|
+
}
|
|
2965
3005
|
const ENTITY_DIRECTORY = {
|
|
2966
3006
|
person: "people",
|
|
2967
3007
|
project: "projects",
|
|
@@ -2973,14 +3013,18 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
|
|
|
2973
3013
|
if (entityType in ENTITY_DIRECTORY && isInitialized()) {
|
|
2974
3014
|
const storageConfig = getStorageConfig();
|
|
2975
3015
|
if (storageConfig.backend === "gcs") {
|
|
2976
|
-
const
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3016
|
+
for (const tryId of entityIdLookupOrder(entityId)) {
|
|
3017
|
+
const gcsEntity = await findContextEntityInGcs(entityType, tryId);
|
|
3018
|
+
if (gcsEntity) {
|
|
3019
|
+
const idForUri = typeof gcsEntity.id === "string" ? String(gcsEntity.id).trim() : tryId;
|
|
3020
|
+
const toDump = entityType === "project" ? stripProjectPlanArraysForResource(gcsEntity) : gcsEntity;
|
|
3021
|
+
const yamlContent2 = yaml.dump(toDump);
|
|
3022
|
+
return {
|
|
3023
|
+
uri: buildEntityUri(entityType, idForUri),
|
|
3024
|
+
mimeType: "application/yaml",
|
|
3025
|
+
text: yamlContent2
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
2984
3028
|
}
|
|
2985
3029
|
}
|
|
2986
3030
|
}
|
|
@@ -3009,41 +3053,49 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
|
|
|
3009
3053
|
});
|
|
3010
3054
|
throw new Error(`No Protokoll context found. Expected .protokoll/ or context dirs in ${searchDir}`);
|
|
3011
3055
|
}
|
|
3012
|
-
const
|
|
3056
|
+
const lookupEntityById = (candidate, forId) => {
|
|
3013
3057
|
switch (entityType) {
|
|
3014
3058
|
case "person":
|
|
3015
|
-
return candidate.getPerson(
|
|
3059
|
+
return candidate.getPerson(forId);
|
|
3016
3060
|
case "project":
|
|
3017
|
-
return candidate.getProject(
|
|
3061
|
+
return candidate.getProject(forId);
|
|
3018
3062
|
case "term":
|
|
3019
|
-
return candidate.getTerm(
|
|
3063
|
+
return candidate.getTerm(forId);
|
|
3020
3064
|
case "company":
|
|
3021
|
-
return candidate.getCompany(
|
|
3065
|
+
return candidate.getCompany(forId);
|
|
3022
3066
|
case "ignored":
|
|
3023
|
-
return candidate.getIgnored(
|
|
3067
|
+
return candidate.getIgnored(forId);
|
|
3024
3068
|
default:
|
|
3025
3069
|
throw new Error(`Unknown entity type: ${entityType}`);
|
|
3026
3070
|
}
|
|
3027
3071
|
};
|
|
3028
|
-
|
|
3029
|
-
if (
|
|
3030
|
-
|
|
3072
|
+
const serverContext = isInitialized() ? getContext() : void 0;
|
|
3073
|
+
if (serverContext?.hasContext() && serverContext !== context) {
|
|
3074
|
+
try {
|
|
3075
|
+
await serverContext.reload();
|
|
3076
|
+
} catch (error) {
|
|
3077
|
+
logger$5.debug("entity.read.server_context_reload_failed", {
|
|
3078
|
+
entityType,
|
|
3079
|
+
entityId,
|
|
3080
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
let entity;
|
|
3085
|
+
for (const tryId of entityIdLookupOrder(entityId)) {
|
|
3086
|
+
entity = lookupEntityById(context, tryId);
|
|
3087
|
+
if (entity) {
|
|
3088
|
+
break;
|
|
3089
|
+
}
|
|
3031
3090
|
if (serverContext?.hasContext() && serverContext !== context) {
|
|
3032
|
-
|
|
3033
|
-
await serverContext.reload();
|
|
3034
|
-
} catch (error) {
|
|
3035
|
-
logger$5.debug("entity.read.server_context_reload_failed", {
|
|
3036
|
-
entityType,
|
|
3037
|
-
entityId,
|
|
3038
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
|
-
entity = lookupEntity(serverContext);
|
|
3091
|
+
entity = lookupEntityById(serverContext, tryId);
|
|
3042
3092
|
if (entity) {
|
|
3043
3093
|
logger$5.info("entity.read.server_context_fallback_hit", {
|
|
3044
3094
|
entityType,
|
|
3045
|
-
entityId
|
|
3095
|
+
entityId,
|
|
3096
|
+
tryId
|
|
3046
3097
|
});
|
|
3098
|
+
break;
|
|
3047
3099
|
}
|
|
3048
3100
|
}
|
|
3049
3101
|
}
|
|
@@ -3056,13 +3108,16 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
|
|
|
3056
3108
|
});
|
|
3057
3109
|
throw new Error(`${entityType} "${entityId}" not found`);
|
|
3058
3110
|
}
|
|
3111
|
+
const canonicalId = entity.id;
|
|
3059
3112
|
logger$5.info("entity.read.found", {
|
|
3060
3113
|
entityType,
|
|
3061
|
-
entityId
|
|
3114
|
+
entityId,
|
|
3115
|
+
canonicalId: canonicalId !== entityId ? canonicalId : void 0
|
|
3062
3116
|
});
|
|
3063
|
-
const
|
|
3117
|
+
const payload = entityType === "project" ? stripProjectPlanArraysForResource(entity) : entity;
|
|
3118
|
+
const yamlContent = yaml.dump(payload);
|
|
3064
3119
|
return {
|
|
3065
|
-
uri: buildEntityUri(entityType,
|
|
3120
|
+
uri: buildEntityUri(entityType, canonicalId),
|
|
3066
3121
|
mimeType: "application/yaml",
|
|
3067
3122
|
text: yamlContent
|
|
3068
3123
|
};
|
|
@@ -4471,7 +4526,10 @@ async function getContextInstance(contextDirectory) {
|
|
|
4471
4526
|
return createToolContext(contextDirectory);
|
|
4472
4527
|
}
|
|
4473
4528
|
function normalizeProjectId(value) {
|
|
4474
|
-
|
|
4529
|
+
if (typeof value !== "string") {
|
|
4530
|
+
return "";
|
|
4531
|
+
}
|
|
4532
|
+
return resolveCanonicalEntityId$1(value).trim().toLowerCase();
|
|
4475
4533
|
}
|
|
4476
4534
|
function createAllowedProjectSet(allowedProjectIds) {
|
|
4477
4535
|
return new Set(
|
|
@@ -4485,6 +4543,25 @@ function isProjectInScope(projectId, allowedProjectIds) {
|
|
|
4485
4543
|
function hasScopedProjectReference(projectIds, allowedProjectIds) {
|
|
4486
4544
|
return (projectIds || []).some((projectId) => isProjectInScope(projectId, allowedProjectIds));
|
|
4487
4545
|
}
|
|
4546
|
+
function dedupeByCanonicalEntityId(items) {
|
|
4547
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4548
|
+
for (const item of items) {
|
|
4549
|
+
const key = resolveCanonicalEntityId$1(item.id).toLowerCase();
|
|
4550
|
+
const arr = groups.get(key) ?? [];
|
|
4551
|
+
arr.push(item);
|
|
4552
|
+
groups.set(key, arr);
|
|
4553
|
+
}
|
|
4554
|
+
const out = [];
|
|
4555
|
+
for (const group of groups.values()) {
|
|
4556
|
+
if (group.length === 1) {
|
|
4557
|
+
out.push(group[0]);
|
|
4558
|
+
continue;
|
|
4559
|
+
}
|
|
4560
|
+
const preferred = group.find((r) => r.id === resolveCanonicalEntityId$1(r.id)) ?? group[0];
|
|
4561
|
+
out.push(preferred);
|
|
4562
|
+
}
|
|
4563
|
+
return out;
|
|
4564
|
+
}
|
|
4488
4565
|
async function loadProjects(context) {
|
|
4489
4566
|
const projects = context.getAllProjects();
|
|
4490
4567
|
if (projects.length > 0) {
|
|
@@ -4828,7 +4905,7 @@ async function handleContextStatus(args) {
|
|
|
4828
4905
|
async function handleListProjects(args) {
|
|
4829
4906
|
const context = await getContextInstance(args.contextDirectory);
|
|
4830
4907
|
const scope = await buildProjectScopeState(context, args.allowedProjectIds);
|
|
4831
|
-
let projects = scope?.projects ?? await loadProjects(context);
|
|
4908
|
+
let projects = dedupeByCanonicalEntityId(scope?.projects ?? await loadProjects(context));
|
|
4832
4909
|
if (!args.includeInactive) {
|
|
4833
4910
|
projects = projects.filter((p) => p.active !== false);
|
|
4834
4911
|
}
|
|
@@ -4848,7 +4925,7 @@ async function handleListProjects(args) {
|
|
|
4848
4925
|
offset,
|
|
4849
4926
|
count: paginatedProjects.length,
|
|
4850
4927
|
projects: paginatedProjects.map((p) => ({
|
|
4851
|
-
id: p.id,
|
|
4928
|
+
id: resolveCanonicalEntityId$1(p.id),
|
|
4852
4929
|
name: p.name,
|
|
4853
4930
|
active: p.active !== false,
|
|
4854
4931
|
destination: p.routing?.destination,
|
|
@@ -4861,7 +4938,7 @@ async function handleListProjects(args) {
|
|
|
4861
4938
|
async function handleListPeople(args) {
|
|
4862
4939
|
const context = await getContextInstance(args.contextDirectory);
|
|
4863
4940
|
const scope = await buildProjectScopeState(context, args.allowedProjectIds);
|
|
4864
|
-
let people = await loadPeople(context);
|
|
4941
|
+
let people = dedupeByCanonicalEntityId(await loadPeople(context));
|
|
4865
4942
|
if (scope) {
|
|
4866
4943
|
people = people.filter((person) => scope.associatedPeople.has(person.id));
|
|
4867
4944
|
}
|
|
@@ -4881,7 +4958,7 @@ async function handleListPeople(args) {
|
|
|
4881
4958
|
offset,
|
|
4882
4959
|
count: paginatedPeople.length,
|
|
4883
4960
|
people: paginatedPeople.map((p) => ({
|
|
4884
|
-
id: p.id,
|
|
4961
|
+
id: resolveCanonicalEntityId$1(p.id),
|
|
4885
4962
|
name: p.name,
|
|
4886
4963
|
company: p.company,
|
|
4887
4964
|
role: p.role,
|
|
@@ -4892,7 +4969,7 @@ async function handleListPeople(args) {
|
|
|
4892
4969
|
async function handleListTerms(args) {
|
|
4893
4970
|
const context = await getContextInstance(args.contextDirectory);
|
|
4894
4971
|
const scope = await buildProjectScopeState(context, args.allowedProjectIds);
|
|
4895
|
-
let terms = await loadTerms(context);
|
|
4972
|
+
let terms = dedupeByCanonicalEntityId(await loadTerms(context));
|
|
4896
4973
|
if (scope) {
|
|
4897
4974
|
terms = terms.filter((term) => hasScopedProjectReference(term.projects, scope.allowedProjectIds));
|
|
4898
4975
|
}
|
|
@@ -4912,7 +4989,7 @@ async function handleListTerms(args) {
|
|
|
4912
4989
|
offset,
|
|
4913
4990
|
count: paginatedTerms.length,
|
|
4914
4991
|
terms: paginatedTerms.map((t) => ({
|
|
4915
|
-
id: t.id,
|
|
4992
|
+
id: resolveCanonicalEntityId$1(t.id),
|
|
4916
4993
|
name: t.name,
|
|
4917
4994
|
expansion: t.expansion,
|
|
4918
4995
|
domain: t.domain,
|
|
@@ -4923,7 +5000,7 @@ async function handleListTerms(args) {
|
|
|
4923
5000
|
async function handleListCompanies(args) {
|
|
4924
5001
|
const context = await getContextInstance(args.contextDirectory);
|
|
4925
5002
|
const scope = await buildProjectScopeState(context, args.allowedProjectIds);
|
|
4926
|
-
let companies = await loadCompanies(context);
|
|
5003
|
+
let companies = dedupeByCanonicalEntityId(await loadCompanies(context));
|
|
4927
5004
|
if (scope) {
|
|
4928
5005
|
companies = companies.filter((company) => scope.associatedCompanies.has(company.id));
|
|
4929
5006
|
}
|
|
@@ -4943,7 +5020,7 @@ async function handleListCompanies(args) {
|
|
|
4943
5020
|
offset,
|
|
4944
5021
|
count: paginatedCompanies.length,
|
|
4945
5022
|
companies: paginatedCompanies.map((c) => ({
|
|
4946
|
-
id: c.id,
|
|
5023
|
+
id: resolveCanonicalEntityId$1(c.id),
|
|
4947
5024
|
name: c.name,
|
|
4948
5025
|
fullName: c.fullName,
|
|
4949
5026
|
industry: c.industry,
|
|
@@ -4971,28 +5048,29 @@ async function handleSearchContext(args) {
|
|
|
4971
5048
|
async function handleGetEntity(args) {
|
|
4972
5049
|
const context = await getContextInstance(args.contextDirectory);
|
|
4973
5050
|
const scope = await buildProjectScopeState(context, args.allowedProjectIds);
|
|
5051
|
+
const entityId = resolveCanonicalEntityId$1(args.entityId.trim());
|
|
4974
5052
|
let entity;
|
|
4975
5053
|
switch (args.entityType) {
|
|
4976
5054
|
case "project":
|
|
4977
|
-
entity = findProjectResilient(context,
|
|
5055
|
+
entity = findProjectResilient(context, entityId);
|
|
4978
5056
|
break;
|
|
4979
5057
|
case "person":
|
|
4980
|
-
entity = findPersonResilient(context,
|
|
5058
|
+
entity = findPersonResilient(context, entityId);
|
|
4981
5059
|
break;
|
|
4982
5060
|
case "term":
|
|
4983
|
-
entity = findTermResilient(context,
|
|
5061
|
+
entity = findTermResilient(context, entityId);
|
|
4984
5062
|
break;
|
|
4985
5063
|
case "company":
|
|
4986
|
-
entity = findCompanyResilient(context,
|
|
5064
|
+
entity = findCompanyResilient(context, entityId);
|
|
4987
5065
|
break;
|
|
4988
5066
|
case "ignored":
|
|
4989
|
-
entity = findIgnoredResilient(context,
|
|
5067
|
+
entity = findIgnoredResilient(context, entityId);
|
|
4990
5068
|
break;
|
|
4991
5069
|
default:
|
|
4992
5070
|
throw new Error(`Unknown entity type: ${args.entityType}`);
|
|
4993
5071
|
}
|
|
4994
5072
|
if (scope && !isEntityVisibleInProjectScope(entity, scope)) {
|
|
4995
|
-
throw new Error(`Project-scoped key cannot access ${args.entityType} "${
|
|
5073
|
+
throw new Error(`Project-scoped key cannot access ${args.entityType} "${entityId}".`);
|
|
4996
5074
|
}
|
|
4997
5075
|
const filePath = context.getEntityFilePath(entity);
|
|
4998
5076
|
return {
|
|
@@ -5030,6 +5108,142 @@ async function handlePredictEntities(args) {
|
|
|
5030
5108
|
});
|
|
5031
5109
|
return { success: true, predictions };
|
|
5032
5110
|
}
|
|
5111
|
+
const PROJECT_PLAN_ARRAY_KEYS = ["related_plans", "plans", "riotplan_plans"];
|
|
5112
|
+
function humanizePlanSlug(slug) {
|
|
5113
|
+
const trimmed = slug.trim();
|
|
5114
|
+
const withoutPrefix = trimmed.replace(/^[0-9a-f]{8}-/i, "");
|
|
5115
|
+
const words = withoutPrefix.split(/[-_/]+/).filter(Boolean);
|
|
5116
|
+
if (words.length === 0) {
|
|
5117
|
+
return trimmed;
|
|
5118
|
+
}
|
|
5119
|
+
return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
5120
|
+
}
|
|
5121
|
+
function pickString(obj, keys) {
|
|
5122
|
+
for (const k of keys) {
|
|
5123
|
+
const v = obj[k];
|
|
5124
|
+
if (typeof v === "string" && v.trim().length > 0) {
|
|
5125
|
+
return v.trim();
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
return "";
|
|
5129
|
+
}
|
|
5130
|
+
function pickCreatedMs(obj) {
|
|
5131
|
+
const raw = pickString(obj, ["createdAt", "created_at", "created", "date", "startedAt", "started_at"]);
|
|
5132
|
+
if (!raw) {
|
|
5133
|
+
return NaN;
|
|
5134
|
+
}
|
|
5135
|
+
const t = Date.parse(raw);
|
|
5136
|
+
return Number.isNaN(t) ? NaN : t;
|
|
5137
|
+
}
|
|
5138
|
+
function normalizePlanRow(raw, index) {
|
|
5139
|
+
if (typeof raw === "string") {
|
|
5140
|
+
const id = raw.trim();
|
|
5141
|
+
return {
|
|
5142
|
+
id: id || `row-${index}`,
|
|
5143
|
+
title: humanizePlanSlug(id),
|
|
5144
|
+
stage: "",
|
|
5145
|
+
createdAt: null,
|
|
5146
|
+
createdMs: NaN
|
|
5147
|
+
};
|
|
5148
|
+
}
|
|
5149
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
5150
|
+
const o = raw;
|
|
5151
|
+
const id = pickString(o, ["id", "planId", "plan_id", "folder", "slug", "path", "name"]) || `row-${index}`;
|
|
5152
|
+
const explicitTitle = pickString(o, ["title", "label", "displayName", "planTitle", "summary"]);
|
|
5153
|
+
const title = explicitTitle.length > 0 ? explicitTitle : humanizePlanSlug(
|
|
5154
|
+
pickString(o, ["folder", "slug", "path", "id", "planId", "plan_id"]) || id
|
|
5155
|
+
);
|
|
5156
|
+
const stage = pickString(o, ["stage", "status", "state", "phase", "category"]);
|
|
5157
|
+
const createdRaw = pickString(o, ["createdAt", "created_at", "created", "date", "startedAt"]);
|
|
5158
|
+
const createdMs = pickCreatedMs(o);
|
|
5159
|
+
return {
|
|
5160
|
+
id,
|
|
5161
|
+
title,
|
|
5162
|
+
stage,
|
|
5163
|
+
createdAt: createdRaw || null,
|
|
5164
|
+
createdMs
|
|
5165
|
+
};
|
|
5166
|
+
}
|
|
5167
|
+
return {
|
|
5168
|
+
id: `row-${index}`,
|
|
5169
|
+
title: "",
|
|
5170
|
+
stage: "",
|
|
5171
|
+
createdAt: null,
|
|
5172
|
+
createdMs: NaN
|
|
5173
|
+
};
|
|
5174
|
+
}
|
|
5175
|
+
function collectPlanRowsFromProjectRecord(record) {
|
|
5176
|
+
const rows = [];
|
|
5177
|
+
for (const k of PROJECT_PLAN_ARRAY_KEYS) {
|
|
5178
|
+
const v = record[k];
|
|
5179
|
+
if (Array.isArray(v)) {
|
|
5180
|
+
rows.push(...v);
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
return rows;
|
|
5184
|
+
}
|
|
5185
|
+
const listProjectPlansTool = {
|
|
5186
|
+
name: "protokoll_list_project_plans",
|
|
5187
|
+
description: "List plans associated with a project entity (from related_plans / plans / riotplan_plans on the project YAML). Supports limit/offset pagination. Used by clients instead of embedding full plan lists in the project resource.",
|
|
5188
|
+
inputSchema: {
|
|
5189
|
+
type: "object",
|
|
5190
|
+
properties: {
|
|
5191
|
+
projectId: {
|
|
5192
|
+
type: "string",
|
|
5193
|
+
description: "Project entity UUID"
|
|
5194
|
+
},
|
|
5195
|
+
limit: {
|
|
5196
|
+
type: "number",
|
|
5197
|
+
description: "Page size (default 25, max 100)"
|
|
5198
|
+
},
|
|
5199
|
+
offset: {
|
|
5200
|
+
type: "number",
|
|
5201
|
+
description: "Number of plans to skip (default 0)"
|
|
5202
|
+
},
|
|
5203
|
+
contextDirectory: {
|
|
5204
|
+
type: "string",
|
|
5205
|
+
description: "Path to the .protokoll context directory"
|
|
5206
|
+
}
|
|
5207
|
+
},
|
|
5208
|
+
required: ["projectId"]
|
|
5209
|
+
}
|
|
5210
|
+
};
|
|
5211
|
+
async function handleListProjectPlans(args) {
|
|
5212
|
+
const context = await getContextInstance(args.contextDirectory);
|
|
5213
|
+
const scope = await buildProjectScopeState(context, args.allowedProjectIds);
|
|
5214
|
+
const projectId = resolveCanonicalEntityId$1(args.projectId.trim());
|
|
5215
|
+
const entity = findProjectResilient(context, projectId);
|
|
5216
|
+
if (scope && !isEntityVisibleInProjectScope(entity, scope)) {
|
|
5217
|
+
throw new Error(`Project-scoped key cannot access project "${projectId}".`);
|
|
5218
|
+
}
|
|
5219
|
+
const record = entity;
|
|
5220
|
+
const allRows = collectPlanRowsFromProjectRecord(record);
|
|
5221
|
+
const normalized = allRows.map((r, i) => normalizePlanRow(r, i));
|
|
5222
|
+
normalized.sort((a, b) => {
|
|
5223
|
+
const da = Number.isNaN(a.createdMs) ? -Infinity : a.createdMs;
|
|
5224
|
+
const db = Number.isNaN(b.createdMs) ? -Infinity : b.createdMs;
|
|
5225
|
+
if (db !== da) {
|
|
5226
|
+
return db - da;
|
|
5227
|
+
}
|
|
5228
|
+
return a.title.localeCompare(b.title, void 0, { sensitivity: "base" });
|
|
5229
|
+
});
|
|
5230
|
+
const limit = Math.min(100, Math.max(1, args.limit ?? 25));
|
|
5231
|
+
const offset = Math.max(0, args.offset ?? 0);
|
|
5232
|
+
const page = normalized.slice(offset, offset + limit);
|
|
5233
|
+
const plans = page.map((p) => ({
|
|
5234
|
+
id: p.id,
|
|
5235
|
+
title: p.title,
|
|
5236
|
+
stage: p.stage,
|
|
5237
|
+
createdAt: p.createdAt
|
|
5238
|
+
}));
|
|
5239
|
+
return {
|
|
5240
|
+
total: normalized.length,
|
|
5241
|
+
limit,
|
|
5242
|
+
offset,
|
|
5243
|
+
count: plans.length,
|
|
5244
|
+
plans
|
|
5245
|
+
};
|
|
5246
|
+
}
|
|
5033
5247
|
|
|
5034
5248
|
function isValidUUID(str) {
|
|
5035
5249
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -5234,7 +5448,7 @@ const addProjectTool = {
|
|
|
5234
5448
|
};
|
|
5235
5449
|
const editProjectTool = {
|
|
5236
5450
|
name: "protokoll_edit_project",
|
|
5237
|
-
description: "Edit an existing project with manual modifications. Unlike protokoll_update_project (which regenerates from a source), this allows direct edits: adding specific sounds_like variants, changing routing, modifying classification, managing relationships, etc. For array fields (sounds_like, topics, explicit_phrases, associated_people, associated_companies, children, siblings, related_terms), use add_* to append or remove_* to delete specific values, or use the base field name to replace the entire array.",
|
|
5451
|
+
description: "Edit an existing project with manual modifications. Unlike protokoll_update_project (which regenerates from a source), this allows direct edits: adding specific sounds_like variants, changing routing, modifying classification, managing relationships, etc. For array fields (sounds_like, urls, topics, explicit_phrases, associated_people, associated_companies, children, siblings, related_terms), use add_* to append or remove_* to delete specific values, or use the base field name to replace the entire array.",
|
|
5238
5452
|
inputSchema: {
|
|
5239
5453
|
type: "object",
|
|
5240
5454
|
properties: {
|
|
@@ -5283,6 +5497,21 @@ const editProjectTool = {
|
|
|
5283
5497
|
items: { type: "string" },
|
|
5284
5498
|
description: "Remove these sounds_like variants"
|
|
5285
5499
|
},
|
|
5500
|
+
urls: {
|
|
5501
|
+
type: "array",
|
|
5502
|
+
items: { type: "string" },
|
|
5503
|
+
description: "Replace all project URLs with this list (e.g. org homepage, documentation)"
|
|
5504
|
+
},
|
|
5505
|
+
add_urls: {
|
|
5506
|
+
type: "array",
|
|
5507
|
+
items: { type: "string" },
|
|
5508
|
+
description: "Append these URLs to the project urls list"
|
|
5509
|
+
},
|
|
5510
|
+
remove_urls: {
|
|
5511
|
+
type: "array",
|
|
5512
|
+
items: { type: "string" },
|
|
5513
|
+
description: "Remove these URLs from the project urls list"
|
|
5514
|
+
},
|
|
5286
5515
|
topics: {
|
|
5287
5516
|
type: "array",
|
|
5288
5517
|
items: { type: "string" },
|
|
@@ -5823,12 +6052,19 @@ async function handleEditProject(args) {
|
|
|
5823
6052
|
const context = await createToolContext(args.contextDirectory);
|
|
5824
6053
|
await assertContextAvailableForEntityEdit(context);
|
|
5825
6054
|
const existingProject = findProjectResilient(context, args.id);
|
|
6055
|
+
const existingProjectUrls = existingProject.urls;
|
|
5826
6056
|
const updatedSoundsLike = mergeArray(
|
|
5827
6057
|
existingProject.sounds_like,
|
|
5828
6058
|
args.sounds_like,
|
|
5829
6059
|
args.add_sounds_like,
|
|
5830
6060
|
args.remove_sounds_like
|
|
5831
6061
|
);
|
|
6062
|
+
const updatedUrls = mergeArray(
|
|
6063
|
+
existingProjectUrls,
|
|
6064
|
+
args.urls,
|
|
6065
|
+
args.add_urls,
|
|
6066
|
+
args.remove_urls
|
|
6067
|
+
);
|
|
5832
6068
|
const updatedTopics = mergeArray(
|
|
5833
6069
|
existingProject.classification?.topics,
|
|
5834
6070
|
args.topics,
|
|
@@ -5916,6 +6152,11 @@ async function handleEditProject(args) {
|
|
|
5916
6152
|
} else if (existingProject.sounds_like && (args.sounds_like !== void 0 || args.remove_sounds_like)) {
|
|
5917
6153
|
delete updatedProject.sounds_like;
|
|
5918
6154
|
}
|
|
6155
|
+
if (updatedUrls !== void 0) {
|
|
6156
|
+
updatedProject.urls = updatedUrls;
|
|
6157
|
+
} else if (existingProjectUrls && (args.urls !== void 0 || args.remove_urls)) {
|
|
6158
|
+
delete updatedProject.urls;
|
|
6159
|
+
}
|
|
5919
6160
|
if (updatedTopics !== void 0) {
|
|
5920
6161
|
updatedProject.classification.topics = updatedTopics;
|
|
5921
6162
|
} else if (existingProject.classification?.topics && (args.topics !== void 0 || args.remove_topics)) {
|
|
@@ -5964,6 +6205,9 @@ async function handleEditProject(args) {
|
|
|
5964
6205
|
if (args.sounds_like !== void 0) changes.push(`sounds_like replaced with ${args.sounds_like.length} items`);
|
|
5965
6206
|
if (args.add_sounds_like?.length) changes.push(`added ${args.add_sounds_like.length} sounds_like variants`);
|
|
5966
6207
|
if (args.remove_sounds_like?.length) changes.push(`removed ${args.remove_sounds_like.length} sounds_like variants`);
|
|
6208
|
+
if (args.urls !== void 0) changes.push(`urls replaced with ${args.urls.length} items`);
|
|
6209
|
+
if (args.add_urls?.length) changes.push(`added ${args.add_urls.length} urls`);
|
|
6210
|
+
if (args.remove_urls?.length) changes.push(`removed ${args.remove_urls.length} urls`);
|
|
5967
6211
|
if (args.topics !== void 0) changes.push(`topics replaced with ${args.topics.length} items`);
|
|
5968
6212
|
if (args.add_topics?.length) changes.push(`added ${args.add_topics.length} topics`);
|
|
5969
6213
|
if (args.remove_topics?.length) changes.push(`removed ${args.remove_topics.length} topics`);
|
|
@@ -9935,6 +10179,7 @@ const tools = [
|
|
|
9935
10179
|
// Context Management
|
|
9936
10180
|
contextStatusTool,
|
|
9937
10181
|
listProjectsTool,
|
|
10182
|
+
listProjectPlansTool,
|
|
9938
10183
|
listPeopleTool,
|
|
9939
10184
|
listTermsTool,
|
|
9940
10185
|
listCompaniesTool,
|
|
@@ -10019,6 +10264,10 @@ async function handleToolCall(name, args) {
|
|
|
10019
10264
|
return handleContextStatus(args);
|
|
10020
10265
|
case "protokoll_list_projects":
|
|
10021
10266
|
return handleListProjects(args);
|
|
10267
|
+
case "protokoll_list_project_plans":
|
|
10268
|
+
return handleListProjectPlans(
|
|
10269
|
+
args
|
|
10270
|
+
);
|
|
10022
10271
|
case "protokoll_list_people":
|
|
10023
10272
|
return handleListPeople(args);
|
|
10024
10273
|
case "protokoll_list_terms":
|