@inixiative/foundry-core 0.5.10 → 0.5.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/foundry-core",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -33,7 +33,7 @@
33
33
  "bun": ">=1.4.4"
34
34
  },
35
35
  "peerDependencies": {
36
- "@inixiative/archive": "^0.32.0"
36
+ "@inixiative/archive": "^0.33.0"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@inixiative/archive": {
@@ -41,6 +41,6 @@
41
41
  }
42
42
  },
43
43
  "devDependencies": {
44
- "@inixiative/archive": "^0.32.0"
44
+ "@inixiative/archive": "^0.33.1"
45
45
  }
46
46
  }
@@ -293,7 +293,13 @@ export function secretInstructionPath(path: string, trusted: readonly string[] =
293
293
  name.startsWith('.') ||
294
294
  /^id_[a-z0-9]+/i.test(name) ||
295
295
  /\.(pem|key|p12|pfx|keystore|jks)$/i.test(name) ||
296
- rest.split(sep).some((part) => CREDENTIAL_DIRECTORIES.has(part))
296
+ rest.split(sep).some((part) => CREDENTIAL_DIRECTORIES.has(part)) ||
297
+ // A `.private` path is never read (FEAT-005): a segment named or ending `.private`, or holding `.private.`.
298
+ path
299
+ .split(sep)
300
+ .some(
301
+ (part) => part === '.private' || part.endsWith('.private') || part.includes('.private.'),
302
+ )
297
303
  );
298
304
  }
299
305
 
@@ -221,7 +221,11 @@ export function extractCorrections(
221
221
  return { corrections, secretsScrubbed };
222
222
  }
223
223
 
224
- const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
224
+ /**
225
+ * Credentials by their own shape (a provider's token prefix, a key block): specific enough that a
226
+ * match is a secret. Foundry lifts these from a message even when the person forgot `$$` (FEAT-005).
227
+ */
228
+ const SHAPED_SECRETS: ReadonlyArray<readonly [RegExp, string]> = [
225
229
  [/sk-ant-[A-Za-z0-9_-]{20,}/g, '[REDACTED:anthropic-key]'],
226
230
  [/sk-[A-Za-z0-9_-]{20,}/g, '[REDACTED:api-key]'],
227
231
  [/gh[pousr]_[A-Za-z0-9]{20,}/g, '[REDACTED:github-token]'],
@@ -245,6 +249,10 @@ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
245
249
  ],
246
250
  [/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/g, '[REDACTED:slack-webhook]'],
247
251
  [/\bASIA[A-Z0-9]{16}/g, '[REDACTED:aws-key]'],
252
+ ];
253
+
254
+ /** Credentials by context (an assignment, a header, a URL): a match may be a secret, so it is scrubbed but never lifted. */
255
+ const CONTEXT_SECRETS: ReadonlyArray<readonly [RegExp, string]> = [
248
256
  [/\b(Bearer|Basic|DPoP)\s+[A-Za-z0-9._~+/=-]{16,}/g, '$1 [REDACTED:credential]'],
249
257
  [/\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]+@/gi, '$1[REDACTED:password]@'],
250
258
  [
@@ -257,6 +265,32 @@ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
257
265
  ],
258
266
  ];
259
267
 
268
+ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
269
+ ...SHAPED_SECRETS,
270
+ ...CONTEXT_SECRETS,
271
+ ];
272
+
273
+ /**
274
+ * The shaped credentials safe to lift from a message the person forgot to mark: each never starts
275
+ * inside a word (`task-runner-…` holds `sk-…`), and the two loosest shapes (`sk-…` and
276
+ * `<word>_live_<…>`, which long hyphenated or snake_case names also fit) must carry a digit. Fresh
277
+ * regex objects, so lifting never shares match state with `scrubSecrets`. Scrubbing a false match is
278
+ * harmless; lifting one would take words out of the person's message.
279
+ */
280
+ export const SHAPED_SECRET_PATTERNS: readonly RegExp[] = SHAPED_SECRETS.map(([pattern]) => {
281
+ const loose =
282
+ pattern.source.startsWith('sk-[') || pattern.source.includes('(?:live|test|prod|runtime)');
283
+ const digit = loose ? '(?=[A-Za-z0-9_-]*\\d)' : '';
284
+ return new RegExp(`(?<![A-Za-z0-9_-])${digit}(?:${pattern.source})`, pattern.flags);
285
+ });
286
+
287
+ /** How many credentials known only by context (`PASSWORD=…`, `Bearer …`, a URL password) the text holds. */
288
+ export function contextSecretHits(text: string): number {
289
+ let hits = 0;
290
+ for (const [pattern] of CONTEXT_SECRETS) hits += [...text.matchAll(pattern)].length;
291
+ return hits;
292
+ }
293
+
260
294
  /** Replace credential-shaped substrings. Transcripts are exactly where leaked keys live. */
261
295
  export function scrubSecrets(text: string): { text: string; hits: number } {
262
296
  let hits = 0;
package/src/index.ts CHANGED
@@ -186,6 +186,7 @@ export {
186
186
  type CorrectionDetection,
187
187
  type CorrectionEvent,
188
188
  type CorrectionSignal,
189
+ contextSecretHits,
189
190
  correctionClassifyMessages,
190
191
  correctionId,
191
192
  detectCorrection,
@@ -194,6 +195,7 @@ export {
194
195
  type FeedbackTag,
195
196
  parseCorrectionClassifications,
196
197
  proseOf,
198
+ SHAPED_SECRET_PATTERNS,
197
199
  scrubSecrets,
198
200
  type TranscriptTurn,
199
201
  } from './feedback/correction';
@@ -423,7 +425,6 @@ export {
423
425
  type ThreadPlan,
424
426
  type ThreadPlanItem,
425
427
  type ThreadReference,
426
- type ThreadSetup,
427
428
  type ThreadSnooze,
428
429
  type ThreadSpawn,
429
430
  type ThreadStatus,
package/src/thread.ts CHANGED
@@ -276,12 +276,6 @@ export interface ThreadMeta {
276
276
  /** 1 for a child of a main thread. */
277
277
  depth?: number;
278
278
 
279
- /**
280
- * Start-up of a thread whose Wardens are held in its own process (docs/THREAD-TREE.md spec C):
281
- * turns typed meanwhile wait as `queued` until it is `ready` (or `degraded`).
282
- */
283
- setup?: ThreadSetup;
284
-
285
279
  /** Project that owns this thread. Scopes memory sources and tool calls. */
286
280
  projectId?: string;
287
281
 
@@ -298,15 +292,6 @@ export interface ThreadMeta {
298
292
  archivedAt?: number;
299
293
  }
300
294
 
301
- export interface ThreadSetup {
302
- status: 'starting' | 'ready' | 'degraded';
303
- startedAt: number;
304
- readyAt?: number;
305
- /** Every configured Warden, those holding a spare, and those that did not by the deadline. */
306
- wardens: { expected: number; held: number; failed: string[] };
307
- runtime: 'claude-in-session' | 'codex-in-process' | 'side-session';
308
- }
309
-
310
295
  export interface Dispatch<T = unknown> {
311
296
  readonly agentId: string;
312
297
  readonly timestamp: number;
package/src/types.ts CHANGED
@@ -217,8 +217,6 @@ export interface LLMProvider {
217
217
  * `threadPrefix` starts at once. `messages` end at those prefixes; no decision is made. Optional.
218
218
  */
219
219
  prestart?(messages: LLMMessage[], opts: CompletionOpts): void;
220
- /** A thread closed: release whatever the provider holds for it. Optional. */
221
- releaseThread?(threadId: string): Promise<void>;
222
220
  }
223
221
 
224
222
  export interface EmbeddingProvider {