@agent-workspace/utils 0.5.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.
@@ -0,0 +1,78 @@
1
+ import { REPUTATION_EWMA_ALPHA, REPUTATION_DECAY_RATE, REPUTATION_BASELINE, MS_PER_MONTH, } from "@agent-workspace/core";
2
+ /**
3
+ * Compute confidence from sample size.
4
+ * Formula: confidence = 1 - 1/(1 + sampleSize * 0.1)
5
+ *
6
+ * @param sampleSize - Number of samples
7
+ * @returns Confidence value (0.0 to ~1.0)
8
+ */
9
+ export function computeConfidence(sampleSize) {
10
+ return Math.round((1 - 1 / (1 + sampleSize * 0.1)) * 100) / 100;
11
+ }
12
+ /**
13
+ * Apply time-based decay to a reputation score.
14
+ * Scores decay toward REPUTATION_BASELINE (0.5) over time.
15
+ *
16
+ * @param dim - The reputation dimension to decay
17
+ * @param now - Current date (defaults to now)
18
+ * @param decayRate - Monthly decay rate (defaults to REPUTATION_DECAY_RATE)
19
+ * @returns The decayed score
20
+ */
21
+ export function computeDecayedScore(dim, now = new Date(), decayRate = REPUTATION_DECAY_RATE) {
22
+ const lastSignalDate = new Date(dim.lastSignal);
23
+ const monthsElapsed = (now.getTime() - lastSignalDate.getTime()) / MS_PER_MONTH;
24
+ if (monthsElapsed <= 0)
25
+ return dim.score;
26
+ const decayFactor = Math.exp(-decayRate * monthsElapsed);
27
+ // Decay toward baseline (0.5)
28
+ const decayed = REPUTATION_BASELINE + (dim.score - REPUTATION_BASELINE) * decayFactor;
29
+ return Math.round(decayed * 1000) / 1000;
30
+ }
31
+ /**
32
+ * Update a reputation dimension with a new signal using EWMA.
33
+ *
34
+ * @param existing - Existing dimension (undefined for first signal)
35
+ * @param signalScore - The new signal score (0.0 to 1.0)
36
+ * @param now - Current date (defaults to now)
37
+ * @param alpha - EWMA learning rate (defaults to REPUTATION_EWMA_ALPHA)
38
+ * @returns Updated reputation dimension
39
+ */
40
+ export function updateDimension(existing, signalScore, now = new Date(), alpha = REPUTATION_EWMA_ALPHA) {
41
+ const timestamp = now.toISOString();
42
+ if (!existing) {
43
+ return {
44
+ score: signalScore,
45
+ confidence: computeConfidence(1),
46
+ sampleSize: 1,
47
+ lastSignal: timestamp,
48
+ };
49
+ }
50
+ // Apply decay to old score before EWMA
51
+ const decayed = computeDecayedScore(existing, now);
52
+ const newScore = alpha * signalScore + (1 - alpha) * decayed;
53
+ const newSampleSize = existing.sampleSize + 1;
54
+ return {
55
+ score: Math.round(newScore * 1000) / 1000,
56
+ confidence: computeConfidence(newSampleSize),
57
+ sampleSize: newSampleSize,
58
+ lastSignal: timestamp,
59
+ };
60
+ }
61
+ /**
62
+ * Compute a weighted average of multiple dimension scores.
63
+ *
64
+ * @param scores - Array of [score, weight] tuples
65
+ * @returns Weighted average score
66
+ */
67
+ export function computeWeightedScore(scores) {
68
+ let totalWeight = 0;
69
+ let weightedSum = 0;
70
+ for (const [score, weight] of scores) {
71
+ weightedSum += score * weight;
72
+ totalWeight += weight;
73
+ }
74
+ if (totalWeight === 0)
75
+ return REPUTATION_BASELINE;
76
+ return Math.round((weightedSum / totalWeight) * 1000) / 1000;
77
+ }
78
+ //# sourceMappingURL=reputation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reputation.js","sourceRoot":"","sources":["../src/reputation.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,GAEb,MAAM,uBAAuB,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AAClE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAwB,EACxB,MAAY,IAAI,IAAI,EAAE,EACtB,YAAoB,qBAAqB;IAEzC,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,cAAc,CAAC,OAAO,EAAE,CAAC,GAAG,YAAY,CAAC;IAEhF,IAAI,aAAa,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC;IAEzC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,aAAa,CAAC,CAAC;IAEzD,8BAA8B;IAC9B,MAAM,OAAO,GAAG,mBAAmB,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,mBAAmB,CAAC,GAAG,WAAW,CAAC;IAEtF,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAyC,EACzC,WAAmB,EACnB,MAAY,IAAI,IAAI,EAAE,EACtB,QAAgB,qBAAqB;IAErC,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAEpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC;YAChC,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,SAAS;SACtB,CAAC;IACJ,CAAC;IAED,uCAAuC;IACvC,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,KAAK,GAAG,WAAW,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC;IAC7D,MAAM,aAAa,GAAG,QAAQ,CAAC,UAAU,GAAG,CAAC,CAAC;IAE9C,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI;QACzC,UAAU,EAAE,iBAAiB,CAAC,aAAa,CAAC;QAC5C,UAAU,EAAE,aAAa;QACzB,UAAU,EAAE,SAAS;KACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA+B;IAClE,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACrC,WAAW,IAAI,KAAK,GAAG,MAAM,CAAC;QAC9B,WAAW,IAAI,MAAM,CAAC;IACxB,CAAC;IAED,IAAI,WAAW,KAAK,CAAC;QAAE,OAAO,mBAAmB,CAAC;IAElD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Swarm recruitment logic for finding and assigning qualified agents to roles.
3
+ */
4
+ import type { SwarmRole, SwarmFrontmatter, ReputationProfileFrontmatter } from "@agent-workspace/core";
5
+ /**
6
+ * A candidate agent for a swarm role with qualification status
7
+ */
8
+ export interface RecruitmentCandidate {
9
+ /** Reputation profile slug */
10
+ slug: string;
11
+ /** Agent DID */
12
+ did: string;
13
+ /** Agent name */
14
+ name: string;
15
+ /** Decayed scores for relevant dimensions */
16
+ scores: Record<string, number>;
17
+ /** Whether the agent meets all minimum requirements */
18
+ qualifies: boolean;
19
+ /** Dimensions/domains where the agent is below threshold */
20
+ gaps: string[];
21
+ }
22
+ /**
23
+ * Result of auto-recruitment for a swarm
24
+ */
25
+ export interface RecruitmentResult {
26
+ /** Role name */
27
+ role: string;
28
+ /** DIDs of agents assigned */
29
+ assigned: string[];
30
+ /** Slugs of agents assigned */
31
+ assignedSlugs: string[];
32
+ /** Number of slots still unfilled */
33
+ unfilled: number;
34
+ }
35
+ /**
36
+ * Find candidates for a swarm role from available reputation profiles.
37
+ * Returns all candidates sorted by qualification (qualified first, then by average score).
38
+ *
39
+ * @param role - The swarm role to find candidates for
40
+ * @param profiles - Available reputation profiles
41
+ * @param now - Current date for decay calculation
42
+ * @returns Array of candidates with qualification status
43
+ */
44
+ export declare function findCandidatesForRole(role: SwarmRole, profiles: ReputationProfileFrontmatter[], now?: Date): RecruitmentCandidate[];
45
+ /**
46
+ * Auto-recruit agents to unfilled roles in a swarm.
47
+ * Assigns the best qualified candidates to each role until filled or no more candidates.
48
+ *
49
+ * @param swarm - The swarm to recruit for
50
+ * @param profiles - Available reputation profiles
51
+ * @param now - Current date for decay calculation
52
+ * @returns Array of recruitment results for each role
53
+ */
54
+ export declare function autoRecruitSwarm(swarm: SwarmFrontmatter, profiles: ReputationProfileFrontmatter[], now?: Date): RecruitmentResult[];
55
+ /**
56
+ * Check if a swarm has all roles filled.
57
+ *
58
+ * @param swarm - The swarm to check
59
+ * @returns True if all roles are fully staffed
60
+ */
61
+ export declare function isSwarmFullyStaffed(swarm: SwarmFrontmatter): boolean;
62
+ /**
63
+ * Get swarm staffing summary.
64
+ *
65
+ * @param swarm - The swarm to summarize
66
+ * @returns Object with filled, needed, and total counts
67
+ */
68
+ export declare function getSwarmStaffingSummary(swarm: SwarmFrontmatter): {
69
+ filled: number;
70
+ needed: number;
71
+ total: number;
72
+ byRole: Array<{
73
+ role: string;
74
+ filled: number;
75
+ needed: number;
76
+ }>;
77
+ };
78
+ //# sourceMappingURL=swarm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swarm.d.ts","sourceRoot":"","sources":["../src/swarm.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,4BAA4B,EAE7B,MAAM,uBAAuB,CAAC;AAG/B;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,uDAAuD;IACvD,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6BD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,4BAA4B,EAAE,EACxC,GAAG,GAAE,IAAiB,GACrB,oBAAoB,EAAE,CA4DxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,4BAA4B,EAAE,EACxC,GAAG,GAAE,IAAiB,GACrB,iBAAiB,EAAE,CAkDrB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAOpE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,gBAAgB,GAAG;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE,CAsBA"}
package/dist/swarm.js ADDED
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Swarm recruitment logic for finding and assigning qualified agents to roles.
3
+ */
4
+ import { computeDecayedScore } from "./reputation.js";
5
+ /**
6
+ * Get the decayed score for a dimension from a reputation profile.
7
+ *
8
+ * @param profile - Reputation profile frontmatter
9
+ * @param dimension - Dimension name or "domain-competence:<domain>"
10
+ * @param now - Current date for decay calculation
11
+ * @returns Decayed score or null if dimension not found
12
+ */
13
+ function getDimensionScore(profile, dimension, now) {
14
+ // Handle domain-competence format
15
+ if (dimension.startsWith("domain-competence:")) {
16
+ const domain = dimension.slice("domain-competence:".length);
17
+ const dim = profile.domainCompetence?.[domain];
18
+ if (!dim)
19
+ return null;
20
+ return computeDecayedScore(dim, now);
21
+ }
22
+ // Standard dimension
23
+ const dim = profile.dimensions?.[dimension];
24
+ if (!dim)
25
+ return null;
26
+ return computeDecayedScore(dim, now);
27
+ }
28
+ /**
29
+ * Find candidates for a swarm role from available reputation profiles.
30
+ * Returns all candidates sorted by qualification (qualified first, then by average score).
31
+ *
32
+ * @param role - The swarm role to find candidates for
33
+ * @param profiles - Available reputation profiles
34
+ * @param now - Current date for decay calculation
35
+ * @returns Array of candidates with qualification status
36
+ */
37
+ export function findCandidatesForRole(role, profiles, now = new Date()) {
38
+ const candidates = [];
39
+ for (const profile of profiles) {
40
+ // Skip if already assigned to this role
41
+ if (role.assigned.includes(profile.agentDid)) {
42
+ continue;
43
+ }
44
+ const scores = {};
45
+ const gaps = [];
46
+ let qualifies = true;
47
+ // Check each minimum reputation requirement
48
+ if (role.minReputation) {
49
+ for (const [dimension, minScore] of Object.entries(role.minReputation)) {
50
+ const score = getDimensionScore(profile, dimension, now);
51
+ if (score !== null) {
52
+ scores[dimension] = Math.round(score * 100) / 100;
53
+ if (score < minScore) {
54
+ gaps.push(dimension);
55
+ qualifies = false;
56
+ }
57
+ }
58
+ else {
59
+ // Missing dimension means not qualified
60
+ scores[dimension] = 0;
61
+ gaps.push(dimension);
62
+ qualifies = false;
63
+ }
64
+ }
65
+ }
66
+ candidates.push({
67
+ slug: profile.id.replace("reputation:", ""),
68
+ did: profile.agentDid,
69
+ name: profile.agentName,
70
+ scores,
71
+ qualifies,
72
+ gaps,
73
+ });
74
+ }
75
+ // Sort: qualified first, then by average score descending
76
+ candidates.sort((a, b) => {
77
+ if (a.qualifies !== b.qualifies) {
78
+ return a.qualifies ? -1 : 1;
79
+ }
80
+ const avgA = Object.values(a.scores).length > 0
81
+ ? Object.values(a.scores).reduce((sum, s) => sum + s, 0) / Object.values(a.scores).length
82
+ : 0;
83
+ const avgB = Object.values(b.scores).length > 0
84
+ ? Object.values(b.scores).reduce((sum, s) => sum + s, 0) / Object.values(b.scores).length
85
+ : 0;
86
+ return avgB - avgA;
87
+ });
88
+ return candidates;
89
+ }
90
+ /**
91
+ * Auto-recruit agents to unfilled roles in a swarm.
92
+ * Assigns the best qualified candidates to each role until filled or no more candidates.
93
+ *
94
+ * @param swarm - The swarm to recruit for
95
+ * @param profiles - Available reputation profiles
96
+ * @param now - Current date for decay calculation
97
+ * @returns Array of recruitment results for each role
98
+ */
99
+ export function autoRecruitSwarm(swarm, profiles, now = new Date()) {
100
+ const results = [];
101
+ const usedDids = new Set();
102
+ // Collect already assigned agents
103
+ for (const role of swarm.roles) {
104
+ for (const did of role.assigned) {
105
+ usedDids.add(did);
106
+ }
107
+ }
108
+ for (const role of swarm.roles) {
109
+ const currentCount = role.assigned.length;
110
+ const neededCount = role.count - currentCount;
111
+ if (neededCount <= 0) {
112
+ results.push({
113
+ role: role.name,
114
+ assigned: [],
115
+ assignedSlugs: [],
116
+ unfilled: 0,
117
+ });
118
+ continue;
119
+ }
120
+ // Filter profiles to exclude already used agents
121
+ const availableProfiles = profiles.filter((p) => !usedDids.has(p.agentDid));
122
+ const candidates = findCandidatesForRole(role, availableProfiles, now);
123
+ // Take qualified candidates up to needed count
124
+ const qualified = candidates.filter((c) => c.qualifies);
125
+ const toAssign = qualified.slice(0, neededCount);
126
+ const assignedDids = toAssign.map((c) => c.did);
127
+ const assignedSlugs = toAssign.map((c) => c.slug);
128
+ // Mark these agents as used
129
+ for (const did of assignedDids) {
130
+ usedDids.add(did);
131
+ }
132
+ results.push({
133
+ role: role.name,
134
+ assigned: assignedDids,
135
+ assignedSlugs,
136
+ unfilled: neededCount - toAssign.length,
137
+ });
138
+ }
139
+ return results;
140
+ }
141
+ /**
142
+ * Check if a swarm has all roles filled.
143
+ *
144
+ * @param swarm - The swarm to check
145
+ * @returns True if all roles are fully staffed
146
+ */
147
+ export function isSwarmFullyStaffed(swarm) {
148
+ for (const role of swarm.roles) {
149
+ if (role.assigned.length < role.count) {
150
+ return false;
151
+ }
152
+ }
153
+ return true;
154
+ }
155
+ /**
156
+ * Get swarm staffing summary.
157
+ *
158
+ * @param swarm - The swarm to summarize
159
+ * @returns Object with filled, needed, and total counts
160
+ */
161
+ export function getSwarmStaffingSummary(swarm) {
162
+ let filled = 0;
163
+ let total = 0;
164
+ const byRole = [];
165
+ for (const role of swarm.roles) {
166
+ const roleFilled = role.assigned.length;
167
+ filled += roleFilled;
168
+ total += role.count;
169
+ byRole.push({
170
+ role: role.name,
171
+ filled: roleFilled,
172
+ needed: role.count,
173
+ });
174
+ }
175
+ return {
176
+ filled,
177
+ needed: total - filled,
178
+ total,
179
+ byRole,
180
+ };
181
+ }
182
+ //# sourceMappingURL=swarm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swarm.js","sourceRoot":"","sources":["../src/swarm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Validate an artifact or profile slug.
3
+ * Slugs must be lowercase alphanumeric with hyphens, not starting with hyphen.
4
+ *
5
+ * @param slug - The slug to validate
6
+ * @returns true if valid, false otherwise
7
+ */
8
+ export declare function validateSlug(slug: string): boolean;
9
+ /**
10
+ * Validate and sanitize a slug, throwing an error if invalid.
11
+ *
12
+ * @param slug - The slug to validate
13
+ * @returns The sanitized slug (trimmed, lowercased)
14
+ * @throws Error if slug is invalid
15
+ */
16
+ export declare function sanitizeSlug(slug: string): string;
17
+ /**
18
+ * Validate that a path is within a root directory (prevents directory traversal).
19
+ *
20
+ * @param root - The root directory path
21
+ * @param targetPath - The path to validate (relative or absolute)
22
+ * @returns The normalized absolute path
23
+ * @throws Error if path traversal is detected
24
+ */
25
+ export declare function validatePath(root: string, targetPath: string): string;
26
+ /**
27
+ * Check if a string is a valid ISO 8601 date (YYYY-MM-DD).
28
+ *
29
+ * @param dateStr - The date string to validate
30
+ * @returns true if valid, false otherwise
31
+ */
32
+ export declare function isValidDate(dateStr: string): boolean;
33
+ /**
34
+ * Check if a string is a valid ISO 8601 timestamp.
35
+ *
36
+ * @param timestamp - The timestamp string to validate
37
+ * @returns true if valid, false otherwise
38
+ */
39
+ export declare function isValidTimestamp(timestamp: string): boolean;
40
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMpD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG3D"}
@@ -0,0 +1,73 @@
1
+ import { resolve, relative, isAbsolute } from "node:path";
2
+ /** Pattern for valid slugs (lowercase alphanumeric with hyphens, not starting with hyphen) */
3
+ const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
4
+ /** Maximum slug length */
5
+ const MAX_SLUG_LENGTH = 100;
6
+ /**
7
+ * Validate an artifact or profile slug.
8
+ * Slugs must be lowercase alphanumeric with hyphens, not starting with hyphen.
9
+ *
10
+ * @param slug - The slug to validate
11
+ * @returns true if valid, false otherwise
12
+ */
13
+ export function validateSlug(slug) {
14
+ return SLUG_PATTERN.test(slug) && slug.length <= MAX_SLUG_LENGTH;
15
+ }
16
+ /**
17
+ * Validate and sanitize a slug, throwing an error if invalid.
18
+ *
19
+ * @param slug - The slug to validate
20
+ * @returns The sanitized slug (trimmed, lowercased)
21
+ * @throws Error if slug is invalid
22
+ */
23
+ export function sanitizeSlug(slug) {
24
+ const trimmed = slug.trim().toLowerCase();
25
+ if (!SLUG_PATTERN.test(trimmed)) {
26
+ throw new Error(`Invalid slug: "${slug}". Must be lowercase alphanumeric with hyphens, not starting with hyphen.`);
27
+ }
28
+ if (trimmed.length > MAX_SLUG_LENGTH) {
29
+ throw new Error(`Slug too long: max ${MAX_SLUG_LENGTH} characters`);
30
+ }
31
+ return trimmed;
32
+ }
33
+ /**
34
+ * Validate that a path is within a root directory (prevents directory traversal).
35
+ *
36
+ * @param root - The root directory path
37
+ * @param targetPath - The path to validate (relative or absolute)
38
+ * @returns The normalized absolute path
39
+ * @throws Error if path traversal is detected
40
+ */
41
+ export function validatePath(root, targetPath) {
42
+ const normalized = resolve(root, targetPath);
43
+ const rel = relative(root, normalized);
44
+ // Prevent directory traversal
45
+ if (rel.startsWith("..") || isAbsolute(rel)) {
46
+ throw new Error(`Path traversal detected: ${targetPath}`);
47
+ }
48
+ return normalized;
49
+ }
50
+ /**
51
+ * Check if a string is a valid ISO 8601 date (YYYY-MM-DD).
52
+ *
53
+ * @param dateStr - The date string to validate
54
+ * @returns true if valid, false otherwise
55
+ */
56
+ export function isValidDate(dateStr) {
57
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
58
+ return false;
59
+ }
60
+ const date = new Date(dateStr);
61
+ return !isNaN(date.getTime());
62
+ }
63
+ /**
64
+ * Check if a string is a valid ISO 8601 timestamp.
65
+ *
66
+ * @param timestamp - The timestamp string to validate
67
+ * @returns true if valid, false otherwise
68
+ */
69
+ export function isValidTimestamp(timestamp) {
70
+ const date = new Date(timestamp);
71
+ return !isNaN(date.getTime());
72
+ }
73
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE1D,8FAA8F;AAC9F,MAAM,YAAY,GAAG,sBAAsB,CAAC;AAE5C,0BAA0B;AAC1B,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,eAAe,CAAC;AACnE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,2EAA2E,CAClG,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,sBAAsB,eAAe,aAAa,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,UAAkB;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAEvC,8BAA8B;IAC9B,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { type WorkspaceManifest } from "@agent-workspace/core";
2
+ /**
3
+ * Find workspace root by walking up from startDir looking for .awp/workspace.json.
4
+ *
5
+ * @param startDir - Directory to start searching from (defaults to cwd)
6
+ * @returns The workspace root path, or null if not found
7
+ */
8
+ export declare function findWorkspaceRoot(startDir?: string): Promise<string | null>;
9
+ /**
10
+ * Load workspace manifest from .awp/workspace.json.
11
+ *
12
+ * @param workspaceRoot - The workspace root directory
13
+ * @returns The parsed workspace manifest
14
+ * @throws Error if manifest cannot be read or parsed
15
+ */
16
+ export declare function loadManifest(workspaceRoot: string): Promise<WorkspaceManifest>;
17
+ /**
18
+ * Check if a file exists at the given path.
19
+ *
20
+ * @param path - The file path to check
21
+ * @returns true if file exists, false otherwise
22
+ */
23
+ export declare function fileExists(path: string): Promise<boolean>;
24
+ /**
25
+ * Get the agent DID from the workspace manifest, or "anonymous" if not set.
26
+ *
27
+ * @param workspaceRoot - The workspace root directory
28
+ * @returns The agent DID or "anonymous"
29
+ */
30
+ export declare function getAgentDid(workspaceRoot: string): Promise<string>;
31
+ /**
32
+ * Read a file with size limit check to prevent memory issues.
33
+ *
34
+ * @param path - The file path to read
35
+ * @param maxSize - Maximum allowed file size in bytes (default: 1MB)
36
+ * @returns The file contents as a string
37
+ * @throws Error if file is too large
38
+ */
39
+ export declare function safeReadFile(path: string, maxSize?: number): Promise<string>;
40
+ /**
41
+ * Resolve workspace root from the AWP_WORKSPACE env var or cwd.
42
+ * This is a simpler version that doesn't walk up directories.
43
+ *
44
+ * @returns The workspace root path
45
+ */
46
+ export declare function getWorkspaceRoot(): string;
47
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK9E;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcjF;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAIpF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOxE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAMjG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
@@ -0,0 +1,93 @@
1
+ import { readFile, access, stat } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { MANIFEST_PATH } from "@agent-workspace/core";
4
+ /** Maximum file size allowed for safe reads (1MB) */
5
+ const MAX_FILE_SIZE = 1024 * 1024;
6
+ /**
7
+ * Find workspace root by walking up from startDir looking for .awp/workspace.json.
8
+ *
9
+ * @param startDir - Directory to start searching from (defaults to cwd)
10
+ * @returns The workspace root path, or null if not found
11
+ */
12
+ export async function findWorkspaceRoot(startDir) {
13
+ let dir = resolve(startDir || process.cwd());
14
+ const root = resolve("/");
15
+ while (dir !== root) {
16
+ const manifestPath = join(dir, MANIFEST_PATH);
17
+ try {
18
+ await access(manifestPath);
19
+ return dir;
20
+ }
21
+ catch {
22
+ dir = resolve(dir, "..");
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Load workspace manifest from .awp/workspace.json.
29
+ *
30
+ * @param workspaceRoot - The workspace root directory
31
+ * @returns The parsed workspace manifest
32
+ * @throws Error if manifest cannot be read or parsed
33
+ */
34
+ export async function loadManifest(workspaceRoot) {
35
+ const manifestPath = join(workspaceRoot, MANIFEST_PATH);
36
+ const raw = await readFile(manifestPath, "utf-8");
37
+ return JSON.parse(raw);
38
+ }
39
+ /**
40
+ * Check if a file exists at the given path.
41
+ *
42
+ * @param path - The file path to check
43
+ * @returns true if file exists, false otherwise
44
+ */
45
+ export async function fileExists(path) {
46
+ try {
47
+ await access(path);
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Get the agent DID from the workspace manifest, or "anonymous" if not set.
56
+ *
57
+ * @param workspaceRoot - The workspace root directory
58
+ * @returns The agent DID or "anonymous"
59
+ */
60
+ export async function getAgentDid(workspaceRoot) {
61
+ try {
62
+ const manifest = await loadManifest(workspaceRoot);
63
+ return manifest.agent?.did || "anonymous";
64
+ }
65
+ catch {
66
+ return "anonymous";
67
+ }
68
+ }
69
+ /**
70
+ * Read a file with size limit check to prevent memory issues.
71
+ *
72
+ * @param path - The file path to read
73
+ * @param maxSize - Maximum allowed file size in bytes (default: 1MB)
74
+ * @returns The file contents as a string
75
+ * @throws Error if file is too large
76
+ */
77
+ export async function safeReadFile(path, maxSize = MAX_FILE_SIZE) {
78
+ const stats = await stat(path);
79
+ if (stats.size > maxSize) {
80
+ throw new Error(`File too large: ${stats.size} bytes (max: ${maxSize})`);
81
+ }
82
+ return readFile(path, "utf-8");
83
+ }
84
+ /**
85
+ * Resolve workspace root from the AWP_WORKSPACE env var or cwd.
86
+ * This is a simpler version that doesn't walk up directories.
87
+ *
88
+ * @returns The workspace root path
89
+ */
90
+ export function getWorkspaceRoot() {
91
+ return process.env.AWP_WORKSPACE || process.cwd();
92
+ }
93
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAC;AAE9E,qDAAqD;AACrD,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,CAAC;AAElC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAiB;IACvD,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE1B,OAAO,GAAG,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;YAC3B,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,aAAqB;IACtD,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,aAAqB;IACrD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,aAAa,CAAC,CAAC;QACnD,OAAO,QAAQ,CAAC,KAAK,EAAE,GAAG,IAAI,WAAW,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,UAAkB,aAAa;IAC9E,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,CAAC,IAAI,gBAAgB,OAAO,GAAG,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AACpD,CAAC"}