@mastra/factory 0.10.2-alpha.0 → 0.10.2-alpha.1
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/CHANGELOG.md +18 -0
- package/LICENSE.md +6 -4
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +4 -1
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +9 -0
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +5 -5
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +2 -0
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +21 -8
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +27 -9
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/org-seed.d.ts +67 -0
- package/dist/session/org-seed.d.ts.map +1 -0
- package/dist/session/org-seed.js +53 -0
- package/dist/session/org-seed.js.map +1 -0
- package/package.json +5 -5
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasResolvedOrg, seedSessionOrg } from "./org-seed.js";
|
|
1
2
|
import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
|
|
2
3
|
//#region src/session/memory-settings-hydration.ts
|
|
3
4
|
/** Default thresholds mirror the TUI `/om` fallbacks. */
|
|
@@ -28,27 +29,44 @@ async function applyStoredMemorySettings(session, record, fallbackOmModelId) {
|
|
|
28
29
|
if (Object.keys(updates).length > 0) await session.state.set(updates);
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
|
-
* Seed a freshly created controller session's
|
|
32
|
-
* from the owner's
|
|
33
|
-
* session-created listener so the seed lands before the caller can
|
|
32
|
+
* Seed a freshly created controller session's tenant org and its
|
|
33
|
+
* observational-memory settings from the owner's source-control row. Registered
|
|
34
|
+
* as a blocking session-created listener so the seed lands before the caller can
|
|
35
|
+
* start a run.
|
|
34
36
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* The org seed matters beyond settings. Subconscious knowledge capture scopes
|
|
38
|
+
* every node and record on `factoryOrgId`; before the SDK refusal guard,
|
|
39
|
+
* missing it made capture substitute the session owner id. For web chat sessions
|
|
40
|
+
* that is the agent controller's own id rather than a tenant, so captured
|
|
41
|
+
* knowledge landed under an org rung no reader ever queries. Same rule as the
|
|
42
|
+
* start coordinator: the org
|
|
43
|
+
* comes from the row the session was created from, never improvised from an
|
|
44
|
+
* owner id.
|
|
45
|
+
*
|
|
46
|
+
* Memory settings for sessions tagged `factoryProjectId` (work/review runs) are
|
|
47
|
+
* owned by the start coordinator, and sessions without a GitHub source-control
|
|
48
|
+
* row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`
|
|
49
|
+
* with their own resolved tenant; both are skipped here. The org seed is not
|
|
50
|
+
* skipped on the tag alone: a web chat session persists `factoryProjectId` from
|
|
51
|
+
* its browser seed, so on resume it carries the tag without ever having been
|
|
52
|
+
* through the coordinator. Best-effort: failures are logged, never thrown.
|
|
40
53
|
*/
|
|
41
54
|
async function hydrateSessionMemorySettings(session, { sourceControl, memorySettings }) {
|
|
42
|
-
|
|
55
|
+
const state = session.state.get() ?? {};
|
|
56
|
+
const isFactoryRun = Boolean(state.factoryProjectId);
|
|
57
|
+
if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;
|
|
43
58
|
try {
|
|
44
59
|
const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());
|
|
60
|
+
await seedSessionOrg(session, record?.orgId);
|
|
45
61
|
if (!record) return;
|
|
62
|
+
if (isFactoryRun) return;
|
|
46
63
|
await applyStoredMemorySettings(session, await memorySettings.get({
|
|
47
64
|
orgId: record.orgId,
|
|
48
65
|
userId: record.userId
|
|
49
66
|
}));
|
|
50
67
|
} catch (error) {
|
|
51
68
|
console.warn("[Factory memory-settings hydration] Unable to apply stored memory settings.", error);
|
|
69
|
+
if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, void 0);
|
|
52
70
|
}
|
|
53
71
|
}
|
|
54
72
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's observational-memory settings
|
|
1
|
+
{"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { hasResolvedOrg, seedSessionOrg } from './org-seed.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n factoryOrgId?: string;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's tenant org and its\n * observational-memory settings from the owner's source-control row. Registered\n * as a blocking session-created listener so the seed lands before the caller can\n * start a run.\n *\n * The org seed matters beyond settings. Subconscious knowledge capture scopes\n * every node and record on `factoryOrgId`; before the SDK refusal guard,\n * missing it made capture substitute the session owner id. For web chat sessions\n * that is the agent controller's own id rather than a tenant, so captured\n * knowledge landed under an org rung no reader ever queries. Same rule as the\n * start coordinator: the org\n * comes from the row the session was created from, never improvised from an\n * owner id.\n *\n * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are\n * owned by the start coordinator, and sessions without a GitHub source-control\n * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`\n * with their own resolved tenant; both are skipped here. The org seed is not\n * skipped on the tag alone: a web chat session persists `factoryProjectId` from\n * its browser seed, so on resume it carries the tag without ever having been\n * through the coordinator. Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\n // A coordinator-hydrated run already carries both halves. Nothing to add.\n if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n // No row, or a row whose org is blank, leaves the session with no tenant.\n // Mark it rather than returning silently: an unmarked projectless factory\n // session is indistinguishable from a local one, and capture would file it\n // under the local scope — the same bug wearing a different rung.\n await seedSessionOrg(session, record?.orgId);\n if (!record) return;\n if (isFactoryRun) return;\n const settings = await memorySettings.get({ orgId: record.orgId, userId: record.userId });\n await applyStoredMemorySettings(session, settings);\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n // A failed lookup is an unresolved org, not an absent one — unless the seed\n // already landed and a later step is what threw.\n if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, undefined);\n }\n}\n"],"mappings":";;;;AAOA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;;;;AAwC5C,eAAsB,0BACpB,SACA,QACA,mBACe;CACf,KAAK,MAAM,QAAQ,CAAC,YAAY,WAAW,GAAY;EAErD,MAAM,UADS,SAAS,aAAa,QAAQ,kBAAkB,QAAQ,qBAC9C,qBAAqB;EAC9C,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ,MAAM,QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,OAAO,CAAC;CAE1D;CACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,UAAyB,CAAC;CAChC,MAAM,uBAAuB,QAAQ,wBAAA;CACrC,IAAI,MAAM,yBAAyB,sBACjC,QAAQ,uBAAuB;CAEjC,MAAM,sBAAsB,QAAQ,uBAAA;CACpC,IAAI,MAAM,wBAAwB,qBAChC,QAAQ,sBAAsB;CAEhC,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,KAAK,MAAM,sBAAsB,YAAY,oBAC3C,QAAQ,qBAAqB;CAE/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;AACtE;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,eAAsB,6BACpB,SACA,EAAE,eAAe,kBACF;CACf,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,eAAe,QAAQ,MAAM,gBAAgB;CAEnD,IAAI,gBAAgB,eAAe,MAAM,YAAY,GAAG;CACxD,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC;EAK3F,MAAM,eAAe,SAAS,QAAQ,KAAK;EAC3C,IAAI,CAAC,QAAQ;EACb,IAAI,cAAc;EAElB,MAAM,0BAA0B,SAAS,MADlB,eAAe,IAAI;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO,CAAC,CACvC;CACnD,SAAS,OAAO;EACd,QAAQ,KAAK,+EAA+E,KAAK;EAGjG,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,MAAM,eAAe,SAAS,KAAA,CAAS;CACjF;AACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tenant organization a session's knowledge is scoped to.
|
|
3
|
+
*
|
|
4
|
+
* Subconscious knowledge capture scopes every node and record on
|
|
5
|
+
* `factoryOrgId`. A session that reaches the capture seam without one used to
|
|
6
|
+
* fall back to the session owner id — for factory sessions the agent
|
|
7
|
+
* controller's own id — so the knowledge landed under an org rung no reader
|
|
8
|
+
* ever queries. The write succeeded and the read could never see it.
|
|
9
|
+
*
|
|
10
|
+
* The fix is that every session-creation path seeds the org it already holds,
|
|
11
|
+
* and a path that cannot resolve one marks the session `factoryOrgUnresolved`
|
|
12
|
+
* so the capture side refuses loudly instead of inventing an identity. "No
|
|
13
|
+
* project id" is not a proxy for "not a factory session" — chat sessions and
|
|
14
|
+
* Slack channel sessions are factory-owned and carry no project id — which is
|
|
15
|
+
* why the unresolved case needs its own explicit marker.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Session-state fields org seeding writes. The index signatures mirror
|
|
19
|
+
* `MastraCodeState` so a concrete `Session.state.set(Partial<MastraCodeState>)`
|
|
20
|
+
* stays assignable to this minimal surface (contravariant parameter check).
|
|
21
|
+
*/
|
|
22
|
+
export interface OrgSeedStateWrites {
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
[key: `subagentModelId_${string}`]: string | undefined;
|
|
25
|
+
factoryOrgId?: string;
|
|
26
|
+
factoryOrgUnresolved?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** The slice of a session needed to seed its organization. */
|
|
29
|
+
export interface OrgSeedableSession {
|
|
30
|
+
state: {
|
|
31
|
+
get: () => Record<string, unknown> | undefined;
|
|
32
|
+
set: (updates: OrgSeedStateWrites) => Promise<void> | void;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A request context carrying the tenant on its `user` key. Slack stamps
|
|
37
|
+
* `{ id, organizationId }` and the GitHub webhook `{ workosId, organizationId }`,
|
|
38
|
+
* so only the shared `organizationId` field may be read here.
|
|
39
|
+
*/
|
|
40
|
+
export interface OrgBearingRequestContext {
|
|
41
|
+
get: (key: string) => unknown;
|
|
42
|
+
}
|
|
43
|
+
/** Read the tenant org off a request context's `user` key, if there is one. */
|
|
44
|
+
export declare function readRequestContextOrgId(requestContext: OrgBearingRequestContext | undefined): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Whether a session state value counts as a resolved organization.
|
|
47
|
+
*
|
|
48
|
+
* The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the
|
|
49
|
+
* recovery guards have to agree with it: a whitespace-only value that reads as
|
|
50
|
+
* truthy here would look resolved to a heal path while capture still refuses,
|
|
51
|
+
* and nothing would ever repair it. Not every seam routes its seed through
|
|
52
|
+
* `seedSessionOrg`, so this cannot be assumed away.
|
|
53
|
+
*/
|
|
54
|
+
export declare function hasResolvedOrg(orgId: unknown): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Seed the session's organization, or mark it unresolved when there is none.
|
|
57
|
+
*
|
|
58
|
+
* An absent, empty, or whitespace-only org is a refusal, not a fallback: a
|
|
59
|
+
* blank org rung is not something canonicalization can save. A successful
|
|
60
|
+
* resolve also clears a stale marker, because the session-start hook runs at
|
|
61
|
+
* most once per session per process and nothing else would ever clear it.
|
|
62
|
+
*
|
|
63
|
+
* Best-effort by contract — every caller is a session-created listener that
|
|
64
|
+
* must not sink a run that is otherwise ready.
|
|
65
|
+
*/
|
|
66
|
+
export declare function seedSessionOrg(session: OrgSeedableSession, orgId: string | null | undefined): Promise<void>;
|
|
67
|
+
//# sourceMappingURL=org-seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-seed.d.ts","sourceRoot":"","sources":["../../src/session/org-seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC5D,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/B;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,wBAAwB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMhH;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjH"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/session/org-seed.ts
|
|
2
|
+
/** Read the tenant org off a request context's `user` key, if there is one. */
|
|
3
|
+
function readRequestContextOrgId(requestContext) {
|
|
4
|
+
if (!requestContext) return void 0;
|
|
5
|
+
const user = requestContext.get("user");
|
|
6
|
+
if (!user || typeof user !== "object") return void 0;
|
|
7
|
+
const orgId = user.organizationId;
|
|
8
|
+
return typeof orgId === "string" ? orgId : void 0;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Whether a session state value counts as a resolved organization.
|
|
12
|
+
*
|
|
13
|
+
* The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the
|
|
14
|
+
* recovery guards have to agree with it: a whitespace-only value that reads as
|
|
15
|
+
* truthy here would look resolved to a heal path while capture still refuses,
|
|
16
|
+
* and nothing would ever repair it. Not every seam routes its seed through
|
|
17
|
+
* `seedSessionOrg`, so this cannot be assumed away.
|
|
18
|
+
*/
|
|
19
|
+
function hasResolvedOrg(orgId) {
|
|
20
|
+
return typeof orgId === "string" && orgId.trim().length > 0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Seed the session's organization, or mark it unresolved when there is none.
|
|
24
|
+
*
|
|
25
|
+
* An absent, empty, or whitespace-only org is a refusal, not a fallback: a
|
|
26
|
+
* blank org rung is not something canonicalization can save. A successful
|
|
27
|
+
* resolve also clears a stale marker, because the session-start hook runs at
|
|
28
|
+
* most once per session per process and nothing else would ever clear it.
|
|
29
|
+
*
|
|
30
|
+
* Best-effort by contract — every caller is a session-created listener that
|
|
31
|
+
* must not sink a run that is otherwise ready.
|
|
32
|
+
*/
|
|
33
|
+
async function seedSessionOrg(session, orgId) {
|
|
34
|
+
const resolved = typeof orgId === "string" ? orgId.trim() : "";
|
|
35
|
+
if (typeof session.state?.get !== "function" || typeof session.state?.set !== "function") return;
|
|
36
|
+
try {
|
|
37
|
+
const state = session.state.get() ?? {};
|
|
38
|
+
if (!resolved) {
|
|
39
|
+
if (state.factoryOrgUnresolved !== true) await session.state.set({ factoryOrgUnresolved: true });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const updates = {};
|
|
43
|
+
if (state.factoryOrgId !== resolved) updates.factoryOrgId = resolved;
|
|
44
|
+
if (state.factoryOrgUnresolved) updates.factoryOrgUnresolved = false;
|
|
45
|
+
if (Object.keys(updates).length > 0) await session.state.set(updates);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn("[Factory org seed] Unable to record the session organization.", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { hasResolvedOrg, readRequestContextOrgId, seedSessionOrg };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=org-seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-seed.js","names":[],"sources":["../../src/session/org-seed.ts"],"sourcesContent":["/**\n * The tenant organization a session's knowledge is scoped to.\n *\n * Subconscious knowledge capture scopes every node and record on\n * `factoryOrgId`. A session that reaches the capture seam without one used to\n * fall back to the session owner id — for factory sessions the agent\n * controller's own id — so the knowledge landed under an org rung no reader\n * ever queries. The write succeeded and the read could never see it.\n *\n * The fix is that every session-creation path seeds the org it already holds,\n * and a path that cannot resolve one marks the session `factoryOrgUnresolved`\n * so the capture side refuses loudly instead of inventing an identity. \"No\n * project id\" is not a proxy for \"not a factory session\" — chat sessions and\n * Slack channel sessions are factory-owned and carry no project id — which is\n * why the unresolved case needs its own explicit marker.\n */\n\n/**\n * Session-state fields org seeding writes. The index signatures mirror\n * `MastraCodeState` so a concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\nexport interface OrgSeedStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n factoryOrgId?: string;\n factoryOrgUnresolved?: boolean;\n}\n\n/** The slice of a session needed to seed its organization. */\nexport interface OrgSeedableSession {\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OrgSeedStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * A request context carrying the tenant on its `user` key. Slack stamps\n * `{ id, organizationId }` and the GitHub webhook `{ workosId, organizationId }`,\n * so only the shared `organizationId` field may be read here.\n */\nexport interface OrgBearingRequestContext {\n get: (key: string) => unknown;\n}\n\n/** Read the tenant org off a request context's `user` key, if there is one. */\nexport function readRequestContextOrgId(requestContext: OrgBearingRequestContext | undefined): string | undefined {\n if (!requestContext) return undefined;\n const user = requestContext.get('user');\n if (!user || typeof user !== 'object') return undefined;\n const orgId = (user as { organizationId?: unknown }).organizationId;\n return typeof orgId === 'string' ? orgId : undefined;\n}\n\n/**\n * Whether a session state value counts as a resolved organization.\n *\n * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the\n * recovery guards have to agree with it: a whitespace-only value that reads as\n * truthy here would look resolved to a heal path while capture still refuses,\n * and nothing would ever repair it. Not every seam routes its seed through\n * `seedSessionOrg`, so this cannot be assumed away.\n */\nexport function hasResolvedOrg(orgId: unknown): boolean {\n return typeof orgId === 'string' && orgId.trim().length > 0;\n}\n\n/**\n * Seed the session's organization, or mark it unresolved when there is none.\n *\n * An absent, empty, or whitespace-only org is a refusal, not a fallback: a\n * blank org rung is not something canonicalization can save. A successful\n * resolve also clears a stale marker, because the session-start hook runs at\n * most once per session per process and nothing else would ever clear it.\n *\n * Best-effort by contract — every caller is a session-created listener that\n * must not sink a run that is otherwise ready.\n */\nexport async function seedSessionOrg(session: OrgSeedableSession, orgId: string | null | undefined): Promise<void> {\n const resolved = typeof orgId === 'string' ? orgId.trim() : '';\n // Some session shapes (approval stubs, lightweight doubles) carry no state at\n // all. There is nothing to seed and nothing to mark, so this is not a warning.\n if (typeof session.state?.get !== 'function' || typeof session.state?.set !== 'function') return;\n try {\n const state = session.state.get() ?? {};\n if (!resolved) {\n if (state.factoryOrgUnresolved !== true) {\n await session.state.set({ factoryOrgUnresolved: true });\n }\n return;\n }\n const updates: OrgSeedStateWrites = {};\n if (state.factoryOrgId !== resolved) updates.factoryOrgId = resolved;\n if (state.factoryOrgUnresolved) updates.factoryOrgUnresolved = false;\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n } catch (error) {\n console.warn('[Factory org seed] Unable to record the session organization.', error);\n }\n}\n"],"mappings":";;AA+CA,SAAgB,wBAAwB,gBAA0E;CAChH,IAAI,CAAC,gBAAgB,OAAO,KAAA;CAC5B,MAAM,OAAO,eAAe,IAAI,MAAM;CACtC,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,KAAA;CAC9C,MAAM,QAAS,KAAsC;CACrD,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;;;;;;;;;;AAWA,SAAgB,eAAe,OAAyB;CACtD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS;AAC5D;;;;;;;;;;;;AAaA,eAAsB,eAAe,SAA6B,OAAiD;CACjH,MAAM,WAAW,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;CAG5D,IAAI,OAAO,QAAQ,OAAO,QAAQ,cAAc,OAAO,QAAQ,OAAO,QAAQ,YAAY;CAC1F,IAAI;EACF,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;EACtC,IAAI,CAAC,UAAU;GACb,IAAI,MAAM,yBAAyB,MACjC,MAAM,QAAQ,MAAM,IAAI,EAAE,sBAAsB,KAAK,CAAC;GAExD;EACF;EACA,MAAM,UAA8B,CAAC;EACrC,IAAI,MAAM,iBAAiB,UAAU,QAAQ,eAAe;EAC5D,IAAI,MAAM,sBAAsB,QAAQ,uBAAuB;EAC/D,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;CACtE,SAAS,OAAO;EACd,QAAQ,KAAK,iEAAiE,KAAK;CACrF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.10.2-alpha.
|
|
3
|
+
"version": "0.10.2-alpha.1",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"zod": "^4.3.6",
|
|
54
54
|
"@mastra/auth-studio": "1.3.4",
|
|
55
55
|
"@mastra/auth-workos": "1.6.4",
|
|
56
|
-
"@mastra/
|
|
56
|
+
"@mastra/code-sdk": "1.5.2-alpha.1",
|
|
57
57
|
"@mastra/slack": "1.6.2",
|
|
58
|
-
"@mastra/
|
|
58
|
+
"@mastra/core": "1.63.1-alpha.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "22.20.1",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"vitest": "4.1.10",
|
|
67
67
|
"@internal/lint": "0.0.127",
|
|
68
68
|
"@mastra/libsql": "1.22.0",
|
|
69
|
-
"@
|
|
70
|
-
"@
|
|
69
|
+
"@internal/types-builder": "0.0.102",
|
|
70
|
+
"@mastra/pg": "1.22.0"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22.19.0"
|