@pikku/core 0.12.24 → 0.12.25

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/data-classification.d.ts +16 -0
  3. package/dist/data-classification.js +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/services/gopass-secrets.d.ts +2 -35
  6. package/dist/services/gopass-secrets.js +10 -40
  7. package/dist/services/local-secrets.d.ts +2 -36
  8. package/dist/services/local-secrets.js +3 -52
  9. package/dist/services/local-variables.d.ts +3 -5
  10. package/dist/services/local-variables.js +10 -10
  11. package/dist/services/scoped-secret-service.d.ts +2 -3
  12. package/dist/services/scoped-secret-service.js +2 -6
  13. package/dist/services/secret-service.d.ts +5 -12
  14. package/dist/services/typed-secret-service.d.ts +3 -4
  15. package/dist/services/typed-secret-service.js +2 -5
  16. package/dist/services/typed-variables-service.d.ts +3 -6
  17. package/dist/services/typed-variables-service.js +0 -6
  18. package/dist/services/variables-service.d.ts +2 -4
  19. package/dist/testing/service-tests.js +13 -13
  20. package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
  21. package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
  22. package/dist/wirings/oauth2/oauth2-client.js +3 -3
  23. package/package.json +1 -1
  24. package/src/data-classification.ts +15 -0
  25. package/src/index.ts +9 -0
  26. package/src/services/gopass-secrets.ts +10 -42
  27. package/src/services/local-secrets.test.ts +10 -10
  28. package/src/services/local-secrets.ts +11 -59
  29. package/src/services/local-variables.test.ts +4 -4
  30. package/src/services/local-variables.ts +12 -17
  31. package/src/services/scoped-secret-service.test.ts +10 -11
  32. package/src/services/scoped-secret-service.ts +4 -9
  33. package/src/services/secret-service.ts +5 -12
  34. package/src/services/typed-secret-service.test.ts +16 -17
  35. package/src/services/typed-secret-service.ts +5 -9
  36. package/src/services/typed-variables-service.test.ts +5 -5
  37. package/src/services/typed-variables-service.ts +5 -17
  38. package/src/services/variables-service.ts +2 -4
  39. package/src/testing/service-tests.ts +13 -13
  40. package/src/wirings/ai-agent/ai-agent-prepare.ts +7 -2
  41. package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
  42. package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
  43. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  44. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.12.25
2
+
3
+ ### Patch Changes
4
+
5
+ - 665bdb0: Add end-to-end data classification for SQLite and Postgres projects.
6
+
7
+ **Core (`@pikku/core`):** New `Private<T>` and `Secret<T>` intersection brands, `ClassificationManifest`, `ColumnClassification`, and `AnonymizeStrategy` types exported from `data-classification.ts`.
8
+
9
+ **CLI (`@pikku/cli`):**
10
+ - SQL comment annotations: `-- @public`, `-- @private[:strategy]`, `-- @secret[:strategy]` on `CREATE TABLE` columns and `ALTER TABLE ... ADD COLUMN` statements. Unannotated columns default to `private`.
11
+ - `pikku db migrate` now emits a `classification.gen.ts` manifest alongside `schema.d.ts`.
12
+ - New `pikku db audit` command — prints a per-column classification summary and warns on `private`/`secret` columns with no anonymize strategy.
13
+ - Postgres dialect support in `resolveDb`, `PostgresMigrationExecutor`, and `PostgresIntrospector`.
14
+
15
+ **Inspector (`@pikku/inspector`):** New PKU910 check — `findPiiPaths()` walks inferred function return types looking for `__pii__` brands (including inside `Array<T>`, `Record<K,V>`, and index signatures) and fails the build if a function exposes branded fields in its output.
16
+
1
17
  ## 0.12.24
2
18
 
3
19
  ### Patch Changes
@@ -0,0 +1,16 @@
1
+ export type Private<T> = T & {
2
+ readonly __pii__: 'private';
3
+ };
4
+ export type Secret<T> = T & {
5
+ readonly __pii__: 'secret';
6
+ };
7
+ export type Classification = 'public' | 'private' | 'secret';
8
+ export type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null;
9
+ export interface ColumnClassification {
10
+ classification: Classification;
11
+ anonymize_strategy: AnonymizeStrategy;
12
+ }
13
+ export type ClassificationManifest = {
14
+ version: 1;
15
+ tables: Record<string, Record<string, ColumnClassification>>;
16
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -50,3 +50,4 @@ export { isSerializable, stopSingletonServices } from './utils.js';
50
50
  export { getSingletonServices, getCreateWireServices } from './pikku-state.js';
51
51
  export { type ScheduledTaskInfo, type ScheduledTaskSummary, } from './services/scheduler-service.js';
52
52
  export { SchedulerService } from './services/scheduler-service.js';
53
+ export type { Private, Secret, Classification, AnonymizeStrategy, ColumnClassification, ClassificationManifest, } from './data-classification.js';
@@ -5,43 +5,10 @@ import type { SecretService } from './secret-service.js';
5
5
  */
6
6
  export declare class GopassSecretService implements SecretService {
7
7
  private prefix;
8
- /**
9
- * Creates an instance of GopassSecretService.
10
- * @param prefix - Optional prefix for all secret keys (e.g., 'pikku/' will look up 'pikku/mykey' for key 'mykey')
11
- */
12
8
  constructor(prefix?: string);
13
9
  private getFullKey;
14
- /**
15
- * Retrieves a secret by key and parses it as JSON.
16
- * @param key - The key of the secret to retrieve.
17
- * @returns A promise that resolves to the parsed secret value.
18
- * @throws {Error} If the secret is not found or gopass fails.
19
- */
20
- getSecretJSON<R>(key: string): Promise<R>;
21
- /**
22
- * Retrieves a secret by key as a string.
23
- * @param key - The key of the secret to retrieve.
24
- * @returns A promise that resolves to the secret value.
25
- * @throws {Error} If the secret is not found or gopass fails.
26
- */
27
- getSecret(key: string): Promise<string>;
28
- /**
29
- * Checks if a secret exists without throwing.
30
- * @param key - The key of the secret to check.
31
- * @returns A promise that resolves to true if the secret exists.
32
- */
10
+ getSecret<T = string>(key: string): Promise<T>;
33
11
  hasSecret(key: string): Promise<boolean>;
34
- /**
35
- * Stores a JSON value as a secret in gopass.
36
- * @param key - The key to store the secret under.
37
- * @param value - The JSON value to store.
38
- * @returns A promise that resolves when the secret is stored.
39
- */
40
- setSecretJSON(key: string, value: unknown): Promise<void>;
41
- /**
42
- * Deletes a secret from gopass.
43
- * @param key - The key of the secret to delete.
44
- * @returns A promise that resolves when the secret is deleted.
45
- */
12
+ setSecret(key: string, value: unknown): Promise<void>;
46
13
  deleteSecret(key: string): Promise<void>;
47
14
  }
@@ -5,10 +5,6 @@ import { execFileSync } from 'child_process';
5
5
  */
6
6
  export class GopassSecretService {
7
7
  prefix;
8
- /**
9
- * Creates an instance of GopassSecretService.
10
- * @param prefix - Optional prefix for all secret keys (e.g., 'pikku/' will look up 'pikku/mykey' for key 'mykey')
11
- */
12
8
  constructor(prefix = '') {
13
9
  this.prefix = prefix;
14
10
  }
@@ -18,38 +14,23 @@ export class GopassSecretService {
18
14
  }
19
15
  return this.prefix ? `${this.prefix}${key}` : key;
20
16
  }
21
- /**
22
- * Retrieves a secret by key and parses it as JSON.
23
- * @param key - The key of the secret to retrieve.
24
- * @returns A promise that resolves to the parsed secret value.
25
- * @throws {Error} If the secret is not found or gopass fails.
26
- */
27
- async getSecretJSON(key) {
28
- const value = await this.getSecret(key);
29
- return JSON.parse(value);
30
- }
31
- /**
32
- * Retrieves a secret by key as a string.
33
- * @param key - The key of the secret to retrieve.
34
- * @returns A promise that resolves to the secret value.
35
- * @throws {Error} If the secret is not found or gopass fails.
36
- */
37
17
  async getSecret(key) {
38
18
  const fullKey = this.getFullKey(key);
39
19
  try {
40
- return execFileSync('gopass', ['show', '-o', fullKey], {
20
+ const raw = execFileSync('gopass', ['show', '-o', fullKey], {
41
21
  encoding: 'utf8',
42
22
  }).trim();
23
+ try {
24
+ return JSON.parse(raw);
25
+ }
26
+ catch {
27
+ return raw;
28
+ }
43
29
  }
44
30
  catch (error) {
45
31
  throw new Error(`Secret Not Found: ${key}`, { cause: error });
46
32
  }
47
33
  }
48
- /**
49
- * Checks if a secret exists without throwing.
50
- * @param key - The key of the secret to check.
51
- * @returns A promise that resolves to true if the secret exists.
52
- */
53
34
  async hasSecret(key) {
54
35
  const fullKey = this.getFullKey(key);
55
36
  try {
@@ -60,19 +41,13 @@ export class GopassSecretService {
60
41
  return false;
61
42
  }
62
43
  }
63
- /**
64
- * Stores a JSON value as a secret in gopass.
65
- * @param key - The key to store the secret under.
66
- * @param value - The JSON value to store.
67
- * @returns A promise that resolves when the secret is stored.
68
- */
69
- async setSecretJSON(key, value) {
44
+ async setSecret(key, value) {
70
45
  const fullKey = this.getFullKey(key);
71
- const jsonValue = JSON.stringify(value);
46
+ const encoded = typeof value === 'string' ? value : JSON.stringify(value);
72
47
  try {
73
48
  execFileSync('gopass', ['insert', '-f', fullKey], {
74
49
  encoding: 'utf8',
75
- input: jsonValue,
50
+ input: encoded,
76
51
  stdio: ['pipe', 'pipe', 'pipe'],
77
52
  });
78
53
  }
@@ -80,11 +55,6 @@ export class GopassSecretService {
80
55
  throw new Error(`Failed to set secret: ${key}`, { cause: error });
81
56
  }
82
57
  }
83
- /**
84
- * Deletes a secret from gopass.
85
- * @param key - The key of the secret to delete.
86
- * @returns A promise that resolves when the secret is deleted.
87
- */
88
58
  async deleteSecret(key) {
89
59
  const fullKey = this.getFullKey(key);
90
60
  try {
@@ -8,43 +8,9 @@ export declare class LocalSecretService implements SecretService {
8
8
  private variables;
9
9
  private localSecrets;
10
10
  private parseSecret;
11
- /**
12
- * Creates an instance of LocalSecretService.
13
- */
14
11
  constructor(variables?: VariablesService);
15
- /**
16
- * Retrieves a secret by key.
17
- * Checks local storage first, then falls back to environment variables.
18
- * @param key - The key of the secret to retrieve.
19
- * @returns A promise that resolves to the secret value.
20
- * @throws {Error} If the secret is not found.
21
- */
22
- getSecretJSON<R>(key: string): Promise<R>;
23
- /**
24
- * Retrieves a secret by key.
25
- * Checks local storage first, then falls back to environment variables.
26
- * @param key - The key of the secret to retrieve.
27
- * @returns A promise that resolves to the secret value.
28
- * @throws {Error} If the secret is not found.
29
- */
30
- getSecret(key: string): Promise<string>;
31
- /**
32
- * Stores a JSON value as a secret in local storage.
33
- * @param key - The key to store the secret under.
34
- * @param value - The JSON value to store.
35
- * @returns A promise that resolves when the secret is stored.
36
- */
37
- setSecretJSON(key: string, value: unknown): Promise<void>;
38
- /**
39
- * Checks if a secret exists without throwing.
40
- * @param key - The key of the secret to check.
41
- * @returns A promise that resolves to true if the secret exists.
42
- */
12
+ getSecret<T = string>(key: string): Promise<T>;
13
+ setSecret(key: string, value: unknown): Promise<void>;
43
14
  hasSecret(key: string): Promise<boolean>;
44
- /**
45
- * Deletes a secret from local storage.
46
- * @param key - The key of the secret to delete.
47
- * @returns A promise that resolves when the secret is deleted.
48
- */
49
15
  deleteSecret(key: string): Promise<void>;
50
16
  }
@@ -6,7 +6,6 @@ import { LocalVariablesService } from './local-variables.js';
6
6
  export class LocalSecretService {
7
7
  variables;
8
8
  localSecrets = new Map();
9
- // TODO: Drop this fallback once secrets and variables expose aligned typed/raw access paths again.
10
9
  parseSecret(raw) {
11
10
  try {
12
11
  return JSON.parse(raw);
@@ -15,66 +14,23 @@ export class LocalSecretService {
15
14
  return raw;
16
15
  }
17
16
  }
18
- /**
19
- * Creates an instance of LocalSecretService.
20
- */
21
17
  constructor(variables = new LocalVariablesService()) {
22
18
  this.variables = variables;
23
19
  }
24
- /**
25
- * Retrieves a secret by key.
26
- * Checks local storage first, then falls back to environment variables.
27
- * @param key - The key of the secret to retrieve.
28
- * @returns A promise that resolves to the secret value.
29
- * @throws {Error} If the secret is not found.
30
- */
31
- async getSecretJSON(key) {
32
- // Check local storage first
20
+ async getSecret(key) {
33
21
  const localValue = this.localSecrets.get(key);
34
22
  if (localValue) {
35
23
  return this.parseSecret(localValue);
36
24
  }
37
- // Fall back to environment variables
38
25
  const value = await this.variables.get(key);
39
26
  if (value) {
40
27
  return this.parseSecret(value);
41
28
  }
42
29
  throw new Error('Requested secret not found');
43
30
  }
44
- /**
45
- * Retrieves a secret by key.
46
- * Checks local storage first, then falls back to environment variables.
47
- * @param key - The key of the secret to retrieve.
48
- * @returns A promise that resolves to the secret value.
49
- * @throws {Error} If the secret is not found.
50
- */
51
- async getSecret(key) {
52
- // Check local storage first
53
- const localValue = this.localSecrets.get(key);
54
- if (localValue) {
55
- return localValue;
56
- }
57
- // Fall back to environment variables
58
- const value = await this.variables.get(key);
59
- if (value) {
60
- return value;
61
- }
62
- throw new Error('Requested secret not found');
63
- }
64
- /**
65
- * Stores a JSON value as a secret in local storage.
66
- * @param key - The key to store the secret under.
67
- * @param value - The JSON value to store.
68
- * @returns A promise that resolves when the secret is stored.
69
- */
70
- async setSecretJSON(key, value) {
71
- this.localSecrets.set(key, JSON.stringify(value));
31
+ async setSecret(key, value) {
32
+ this.localSecrets.set(key, typeof value === 'string' ? value : JSON.stringify(value));
72
33
  }
73
- /**
74
- * Checks if a secret exists without throwing.
75
- * @param key - The key of the secret to check.
76
- * @returns A promise that resolves to true if the secret exists.
77
- */
78
34
  async hasSecret(key) {
79
35
  if (this.localSecrets.has(key)) {
80
36
  return true;
@@ -82,11 +38,6 @@ export class LocalSecretService {
82
38
  const value = await this.variables.get(key);
83
39
  return value !== undefined && value !== null && value !== '';
84
40
  }
85
- /**
86
- * Deletes a secret from local storage.
87
- * @param key - The key of the secret to delete.
88
- * @returns A promise that resolves when the secret is deleted.
89
- */
90
41
  async deleteSecret(key) {
91
42
  this.localSecrets.delete(key);
92
43
  }
@@ -2,11 +2,9 @@ import type { VariablesService } from './variables-service.js';
2
2
  export declare class LocalVariablesService implements VariablesService {
3
3
  private variables;
4
4
  constructor(variables?: Record<string, string | undefined>);
5
- getAll(): Promise<Record<string, string | undefined>> | Record<string, string | undefined>;
6
- get(name: string): Promise<string | undefined> | string | undefined;
7
- getJSON<T = unknown>(name: string): T | undefined;
8
- set(name: string, value: string): void;
9
- setJSON(name: string, value: unknown): void;
5
+ getAll(): Record<string, string | undefined>;
6
+ get<T = string>(name: string): T | undefined;
7
+ set(name: string, value: unknown): void;
10
8
  has(name: string): boolean;
11
9
  delete(name: string): void;
12
10
  }
@@ -7,19 +7,19 @@ export class LocalVariablesService {
7
7
  return this.variables || {};
8
8
  }
9
9
  get(name) {
10
- return this.variables[name];
11
- }
12
- getJSON(name) {
13
- const value = this.variables[name];
14
- if (value === undefined)
10
+ const raw = this.variables[name];
11
+ if (raw === undefined)
15
12
  return undefined;
16
- return JSON.parse(value);
13
+ try {
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return raw;
18
+ }
17
19
  }
18
20
  set(name, value) {
19
- this.variables[name] = value;
20
- }
21
- setJSON(name, value) {
22
- this.variables[name] = JSON.stringify(value);
21
+ this.variables[name] =
22
+ typeof value === 'string' ? value : JSON.stringify(value);
23
23
  }
24
24
  has(name) {
25
25
  return name in this.variables && this.variables[name] !== undefined;
@@ -8,9 +8,8 @@ export declare class ScopedSecretService implements SecretService {
8
8
  private allowedKeys;
9
9
  constructor(secrets: SecretService, allowedKeys: Set<string>);
10
10
  private assertAllowed;
11
- getSecret(key: string): Promise<string>;
12
- getSecretJSON<T = {}>(key: string): Promise<T>;
11
+ getSecret<T = string>(key: string): Promise<T>;
13
12
  hasSecret(key: string): Promise<boolean>;
14
- setSecretJSON(_key: string, _value: unknown): Promise<void>;
13
+ setSecret(_key: string, _value: unknown): Promise<void>;
15
14
  deleteSecret(_key: string): Promise<void>;
16
15
  }
@@ -18,16 +18,12 @@ export class ScopedSecretService {
18
18
  this.assertAllowed(key);
19
19
  return this.secrets.getSecret(key);
20
20
  }
21
- async getSecretJSON(key) {
22
- this.assertAllowed(key);
23
- return this.secrets.getSecretJSON(key);
24
- }
25
21
  async hasSecret(key) {
26
22
  this.assertAllowed(key);
27
23
  return this.secrets.hasSecret(key);
28
24
  }
29
- async setSecretJSON(_key, _value) {
30
- throw new Error('setSecretJSON is not allowed in scoped secret service');
25
+ async setSecret(_key, _value) {
26
+ throw new Error('setSecret is not allowed in scoped secret service');
31
27
  }
32
28
  async deleteSecret(_key) {
33
29
  throw new Error('deleteSecret is not allowed in scoped secret service');
@@ -3,19 +3,12 @@
3
3
  */
4
4
  export interface SecretService {
5
5
  /**
6
- * Retrieves a secret by key and parses it as JSON.
7
- * @param key - The key of the secret to retrieve.
8
- * @returns A promise that resolves to the parsed secret value.
9
- * @throws If the secret is not found.
10
- */
11
- getSecretJSON<Return = {}>(key: string): Promise<Return>;
12
- /**
13
- * Retrieves a secret by key as a string.
6
+ * Retrieves a secret by key, typed as T (defaults to string).
14
7
  * @param key - The key of the secret to retrieve.
15
8
  * @returns A promise that resolves to the secret value.
16
9
  * @throws If the secret is not found.
17
10
  */
18
- getSecret(key: string): Promise<string>;
11
+ getSecret<T = string>(key: string): Promise<T>;
19
12
  /**
20
13
  * Checks if a secret exists without throwing.
21
14
  * @param key - The key of the secret to check.
@@ -23,12 +16,12 @@ export interface SecretService {
23
16
  */
24
17
  hasSecret(key: string): Promise<boolean>;
25
18
  /**
26
- * Stores a JSON value as a secret.
19
+ * Stores a secret value.
27
20
  * @param key - The key to store the secret under.
28
- * @param value - The JSON value to store.
21
+ * @param value - The value to store.
29
22
  * @returns A promise that resolves when the secret is stored.
30
23
  */
31
- setSecretJSON(key: string, value: unknown): Promise<void>;
24
+ setSecret(key: string, value: unknown): Promise<void>;
32
25
  /**
33
26
  * Deletes a secret by key.
34
27
  * @param key - The key of the secret to delete.
@@ -19,11 +19,10 @@ export declare class TypedSecretService<TMap = Record<string, unknown>> implemen
19
19
  private secrets;
20
20
  private credentialsMeta;
21
21
  constructor(secrets: SecretService, credentialsMeta: Record<string, CredentialMeta>);
22
- getSecretJSON<K extends keyof TMap & string>(key: K): Promise<TMap[K]>;
23
- getSecretJSON<T = unknown>(key: string): Promise<T>;
24
- getSecret(key: string): Promise<string>;
22
+ getSecret<K extends keyof TMap & string>(key: K): Promise<TMap[K]>;
23
+ getSecret<T = string>(key: string): Promise<T>;
25
24
  hasSecret(key: string): Promise<boolean>;
26
- setSecretJSON<K extends string>(key: K, value: K extends keyof TMap ? TMap[K] : unknown): Promise<void>;
25
+ setSecret<K extends string>(key: K, value: K extends keyof TMap ? TMap[K] : unknown): Promise<void>;
27
26
  deleteSecret(key: string): Promise<void>;
28
27
  getAllStatus(): Promise<CredentialStatus[]>;
29
28
  getMissing(): Promise<CredentialStatus[]>;
@@ -5,17 +5,14 @@ export class TypedSecretService {
5
5
  this.secrets = secrets;
6
6
  this.credentialsMeta = credentialsMeta;
7
7
  }
8
- async getSecretJSON(key) {
9
- return this.secrets.getSecretJSON(key);
10
- }
11
8
  async getSecret(key) {
12
9
  return this.secrets.getSecret(key);
13
10
  }
14
11
  async hasSecret(key) {
15
12
  return this.secrets.hasSecret(key);
16
13
  }
17
- async setSecretJSON(key, value) {
18
- return this.secrets.setSecretJSON(key, value);
14
+ async setSecret(key, value) {
15
+ return this.secrets.setSecret(key, value);
19
16
  }
20
17
  async deleteSecret(key) {
21
18
  return this.secrets.deleteSecret(key);
@@ -13,13 +13,10 @@ export declare class TypedVariablesService<TMap = Record<string, unknown>> imple
13
13
  private variables;
14
14
  private variablesMeta;
15
15
  constructor(variables: VariablesService, variablesMeta: Record<string, VariableMeta>);
16
- get(name: keyof TMap & string): Promise<string | undefined> | string | undefined;
17
- get(name: string): Promise<string | undefined> | string | undefined;
18
- getJSON<K extends keyof TMap & string>(name: K): Promise<TMap[K] | undefined> | TMap[K] | undefined;
19
- getJSON<T = unknown>(name: string): Promise<T | undefined> | T | undefined;
16
+ get<K extends keyof TMap & string>(name: K): Promise<TMap[K] | undefined> | TMap[K] | undefined;
17
+ get<T = string>(name: string): Promise<T | undefined> | T | undefined;
20
18
  getAll(): Promise<Record<string, string | undefined>> | Record<string, string | undefined>;
21
- set(name: string, value: string): Promise<void> | void;
22
- setJSON(name: string, value: unknown): Promise<void> | void;
19
+ set(name: string, value: unknown): Promise<void> | void;
23
20
  has(name: string): Promise<boolean> | boolean;
24
21
  delete(name: string): Promise<void> | void;
25
22
  getAllStatus(): Promise<VariableStatus[]>;
@@ -8,18 +8,12 @@ export class TypedVariablesService {
8
8
  get(name) {
9
9
  return this.variables.get(name);
10
10
  }
11
- getJSON(name) {
12
- return this.variables.getJSON(name);
13
- }
14
11
  getAll() {
15
12
  return this.variables.getAll();
16
13
  }
17
14
  set(name, value) {
18
15
  return this.variables.set(name, value);
19
16
  }
20
- setJSON(name, value) {
21
- return this.variables.setJSON(name, value);
22
- }
23
17
  has(name) {
24
18
  return this.variables.has(name);
25
19
  }
@@ -1,9 +1,7 @@
1
1
  export interface VariablesService {
2
- get: (name: string) => Promise<string | undefined> | string | undefined;
3
- getJSON: <T = unknown>(name: string) => Promise<T | undefined> | T | undefined;
2
+ get<T = string>(name: string): Promise<T | undefined> | T | undefined;
4
3
  getAll: () => Promise<Record<string, string | undefined>> | Record<string, string | undefined>;
5
- set: (name: string, value: string) => Promise<void> | void;
6
- setJSON: (name: string, value: unknown) => Promise<void> | void;
4
+ set: (name: string, value: unknown) => Promise<void> | void;
7
5
  has: (name: string) => Promise<boolean> | boolean;
8
6
  delete: (name: string) => Promise<void> | void;
9
7
  }
@@ -517,13 +517,13 @@ export function defineServiceTests(config) {
517
517
  const factory = services.secretService;
518
518
  const kek = 'test-key-encryption-key-32chars!';
519
519
  describe(`SecretService [${name}]`, () => {
520
- test('setSecretJSON and getSecretJSON', async () => {
520
+ test('setSecret and getSecret', async () => {
521
521
  const service = await factory({ key: kek });
522
- await service.setSecretJSON('api-key', {
522
+ await service.setSecret('api-key', {
523
523
  token: 'sk-123',
524
524
  endpoint: 'https://api.example.com',
525
525
  });
526
- const result = await service.getSecretJSON('api-key');
526
+ const result = await service.getSecret('api-key');
527
527
  assert.deepEqual(result, {
528
528
  token: 'sk-123',
529
529
  endpoint: 'https://api.example.com',
@@ -531,9 +531,9 @@ export function defineServiceTests(config) {
531
531
  });
532
532
  test('getSecret returns raw string', async () => {
533
533
  const service = await factory({ key: kek });
534
- await service.setSecretJSON('string-secret', 'plain-value');
534
+ await service.setSecret('string-secret', 'plain-value');
535
535
  const result = await service.getSecret('string-secret');
536
- assert.strictEqual(result, '"plain-value"');
536
+ assert.strictEqual(result, 'plain-value');
537
537
  });
538
538
  test('hasSecret returns true/false', async () => {
539
539
  const service = await factory({ key: kek });
@@ -546,16 +546,16 @@ export function defineServiceTests(config) {
546
546
  message: 'Requested secret not found',
547
547
  });
548
548
  });
549
- test('setSecretJSON upserts existing key', async () => {
549
+ test('setSecret upserts existing key', async () => {
550
550
  const service = await factory({ key: kek });
551
- await service.setSecretJSON('upsert-key', { v: 1 });
552
- await service.setSecretJSON('upsert-key', { v: 2 });
553
- const result = await service.getSecretJSON('upsert-key');
551
+ await service.setSecret('upsert-key', { v: 1 });
552
+ await service.setSecret('upsert-key', { v: 2 });
553
+ const result = await service.getSecret('upsert-key');
554
554
  assert.deepEqual(result, { v: 2 });
555
555
  });
556
556
  test('deleteSecret removes the key', async () => {
557
557
  const service = await factory({ key: kek });
558
- await service.setSecretJSON('to-delete', 'bye');
558
+ await service.setSecret('to-delete', 'bye');
559
559
  assert.strictEqual(await service.hasSecret('to-delete'), true);
560
560
  await service.deleteSecret('to-delete');
561
561
  assert.strictEqual(await service.hasSecret('to-delete'), false);
@@ -563,13 +563,13 @@ export function defineServiceTests(config) {
563
563
  test('rotateKEK re-wraps all secrets', async () => {
564
564
  const newKEK = 'new-key-encryption-key-rotated!';
565
565
  const oldService = await factory({ key: kek });
566
- await oldService.setSecretJSON('rotate-test', { important: 'data' });
566
+ await oldService.setSecret('rotate-test', { important: 'data' });
567
567
  const rotatedService = await factory({
568
568
  key: newKEK,
569
569
  keyVersion: 2,
570
570
  previousKey: kek,
571
571
  });
572
- const before = await rotatedService.getSecretJSON('rotate-test');
572
+ const before = await rotatedService.getSecret('rotate-test');
573
573
  assert.deepEqual(before, { important: 'data' });
574
574
  assert.ok(rotatedService.rotateKEK);
575
575
  const count = await rotatedService.rotateKEK();
@@ -578,7 +578,7 @@ export function defineServiceTests(config) {
578
578
  key: newKEK,
579
579
  keyVersion: 2,
580
580
  });
581
- const after = await newOnlyService.getSecretJSON('rotate-test');
581
+ const after = await newOnlyService.getSecret('rotate-test');
582
582
  assert.deepEqual(after, { important: 'data' });
583
583
  });
584
584
  test('rotateKEK throws without previousKey', async () => {
@@ -494,7 +494,10 @@ export async function prepareAgentRun(agentName, input, params, agentSessionMap,
494
494
  const trimmedMessages = trimMessages(allMessages);
495
495
  const aiMiddlewares = agent.aiMiddleware ?? [];
496
496
  const { tools, missingRpcs } = await buildToolDefs(params, agentSessionMap, input.resourceId, resolvedName, packageName, streamContext, aiMiddlewares, agent.agentMode);
497
- const instructions = await buildInstructions(resolvedName, packageName);
497
+ let instructions = await buildInstructions(resolvedName, packageName);
498
+ if (input.context) {
499
+ instructions = `${instructions}\n\nCurrent context (use these identifiers directly in tool calls — do not ask the user for them):\n${input.context}`;
500
+ }
498
501
  const resolved = resolveModelConfig(resolvedName, agent);
499
502
  // Per-request overrides
500
503
  if (input.model) {
@@ -76,6 +76,10 @@ export interface AIAgentInput {
76
76
  attachments?: AIAgentInputAttachment[];
77
77
  model?: string;
78
78
  temperature?: number;
79
+ /** Structured context injected into the system instructions for this request.
80
+ * Use to provide upfront state (e.g. current org/project/branch/deployment)
81
+ * so the agent can call tools without asking the user for identifiers. */
82
+ context?: string;
79
83
  }
80
84
  export interface AIAgentOutput {
81
85
  runId: string;
@@ -98,7 +98,7 @@ export class OAuth2Client {
98
98
  throw new Error('OAuth2 token expired and no refresh token available');
99
99
  }
100
100
  // Load from secrets
101
- const token = await this.secrets.getSecretJSON(this.oauth2Config.tokenSecretId);
101
+ const token = await this.secrets.getSecret(this.oauth2Config.tokenSecretId);
102
102
  this.cachedToken = token;
103
103
  // Check if loaded token is expired and needs refresh
104
104
  if (!this.isTokenValid(token)) {
@@ -167,7 +167,7 @@ export class OAuth2Client {
167
167
  tokenType: data.token_type || 'Bearer',
168
168
  scope: data.scope,
169
169
  };
170
- await this.secrets.setSecretJSON(this.oauth2Config.tokenSecretId, token);
170
+ await this.secrets.setSecret(this.oauth2Config.tokenSecretId, token);
171
171
  this.cachedToken = token;
172
172
  return token;
173
173
  }
@@ -188,7 +188,7 @@ export class OAuth2Client {
188
188
  return this.cachedAppCredential;
189
189
  }
190
190
  this.cachedAppCredential =
191
- await this.secrets.getSecretJSON(this.appCredentialSecretId);
191
+ await this.secrets.getSecret(this.appCredentialSecretId);
192
192
  return this.cachedAppCredential;
193
193
  }
194
194
  // ========================================