@agent-native/core 0.227.0 → 0.228.0-nightly-20261111004142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +6 -2
- package/dist/agent/engine/builder-engine.js +6 -1
- package/dist/agent/engine/tool-call-journal-seed.d.ts +2 -0
- package/dist/agent/engine/tool-call-journal-seed.js +14 -0
- package/dist/agent/harness/store.js +14 -0
- package/dist/agent/production-agent.js +257 -17
- package/dist/agent/reaped-run-thread.js +6 -0
- package/dist/agent/run-manager.js +6 -0
- package/dist/agent/tool-call-journal.d.ts +4 -0
- package/dist/agent/tool-call-journal.js +12 -0
- package/dist/agent/types.d.ts +7 -1
- package/dist/app-config/agent.d.ts +3 -0
- package/dist/app-config/agent.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/application-state/handlers.d.ts +3 -3
- package/dist/application-state/handlers.js +6 -1
- package/dist/application-state/index.d.ts +3 -2
- package/dist/application-state/index.js +3 -2
- package/dist/application-state/last-used-tab.d.ts +11 -0
- package/dist/application-state/last-used-tab.js +43 -0
- package/dist/application-state/script-helpers.d.ts +26 -0
- package/dist/application-state/script-helpers.js +83 -15
- package/dist/application-state/store.d.ts +8 -0
- package/dist/application-state/store.js +16 -0
- package/dist/cli/agent.js +7 -3
- package/dist/cli/code-agent-executor.d.ts +1 -0
- package/dist/cli/code-agent-executor.js +37 -2
- package/dist/cli/code.js +3 -1
- package/dist/client/agent-sidebar-state.d.ts +2 -0
- package/dist/client/browser-tab-use.d.ts +9 -0
- package/dist/client/browser-tab-use.js +112 -0
- package/dist/client/chat/runtime.js +3 -1
- package/dist/client/error-capture.js +33 -1
- package/dist/client/navigation/index.d.ts +1 -0
- package/dist/client/navigation/index.js +1 -0
- package/dist/client/resources/mcp-integration-logos.js +16 -0
- package/dist/client/review/use-review.d.ts +28 -0
- package/dist/client/review/use-review.js +41 -12
- package/dist/client/route-state.js +2 -0
- package/dist/client/sse-event-processor.d.ts +1 -0
- package/dist/client/sse-event-processor.js +7 -1
- package/dist/client/use-db-sync.d.ts +8 -1
- package/dist/client/use-db-sync.js +6 -5
- package/dist/coding-tools/run-code.js +12 -2
- package/dist/coding-tools/sandbox/executions-store.d.ts +1 -1
- package/dist/coding-tools/sandbox/executions-store.js +9 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/data-programs/execute.js +1 -1
- package/dist/db/schema-snapshot.generated.js +17 -3
- package/dist/extensions/url-safety.d.ts +1 -3
- package/dist/extensions/url-safety.js +23 -23
- package/dist/file-upload/actions/upload-image.d.ts +5 -5
- package/dist/file-upload/index.d.ts +1 -1
- package/dist/file-upload/index.js +1 -1
- package/dist/file-upload/s3-client.d.ts +1 -0
- package/dist/file-upload/s3-client.js +70 -13
- package/dist/file-upload/s3.d.ts +9 -0
- package/dist/file-upload/s3.js +14 -1
- package/dist/framework-package-versions.json +8 -8
- package/dist/jobs/run-history.js +10 -3
- package/dist/jobs/scheduler.js +5 -36
- package/dist/localization/core-messages/supplemental/ar-SA.js +35 -0
- package/dist/localization/core-messages/supplemental/de-DE.js +35 -0
- package/dist/localization/core-messages/supplemental/en-US.js +35 -0
- package/dist/localization/core-messages/supplemental/es-ES.js +35 -0
- package/dist/localization/core-messages/supplemental/fr-FR.js +35 -0
- package/dist/localization/core-messages/supplemental/hi-IN.js +35 -0
- package/dist/localization/core-messages/supplemental/ja-JP.js +35 -0
- package/dist/localization/core-messages/supplemental/ko-KR.js +35 -0
- package/dist/localization/core-messages/supplemental/pt-BR.js +35 -0
- package/dist/localization/core-messages/supplemental/zh-CN.js +35 -0
- package/dist/localization/core-messages/supplemental/zh-TW.js +35 -0
- package/dist/localization/default-messages.d.ts +35 -0
- package/dist/localization/default-messages.js +35 -0
- package/dist/localization/mcp-settings-messages.d.ts +35 -0
- package/dist/mcp/actions/list-mcp-client-connections.d.ts +8 -0
- package/dist/mcp/actions/list-mcp-client-connections.js +39 -0
- package/dist/mcp/browser-tab-target.d.ts +16 -0
- package/dist/mcp/browser-tab-target.js +58 -0
- package/dist/mcp/build-server.d.ts +1 -0
- package/dist/mcp/build-server.js +38 -7
- package/dist/mcp/call-attribution.d.ts +4 -1
- package/dist/mcp/call-attribution.js +3 -1
- package/dist/mcp/client-connections.d.ts +6 -0
- package/dist/mcp/client-connections.js +91 -0
- package/dist/mcp/connect-route.js +12 -2
- package/dist/mcp/connect-store.d.ts +3 -0
- package/dist/mcp/connect-store.js +10 -1
- package/dist/mcp/direct-action-call.js +1 -0
- package/dist/mcp/oauth-store.d.ts +6 -0
- package/dist/mcp/oauth-store.js +23 -0
- package/dist/mcp/oauth-token.d.ts +1 -0
- package/dist/mcp/oauth-token.js +1 -0
- package/dist/mcp/server.js +9 -20
- package/dist/mcp-client/config.d.ts +2 -0
- package/dist/mcp-client/manager.js +23 -0
- package/dist/mcp-client/workspace-servers.js +42 -10
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/org/handlers.d.ts +1 -1
- package/dist/org/handlers.js +21 -17
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/provider-api/index.js +17 -36
- package/dist/review/actions/create-review-comment.d.ts +7 -0
- package/dist/review/actions/create-review-comment.js +29 -3
- package/dist/review/actions/delete-review-comment.js +2 -2
- package/dist/review/actions/list-review-comments.d.ts +3 -0
- package/dist/review/actions/list-review-comments.js +41 -17
- package/dist/review/actions/react-to-review-comment.js +2 -2
- package/dist/review/actions/reply-review-comment.d.ts +4 -0
- package/dist/review/actions/reply-review-comment.js +15 -6
- package/dist/review/actions/update-review-comment-anchor.d.ts +6 -0
- package/dist/review/actions/update-review-comment-anchor.js +39 -10
- package/dist/review/actions/update-review-comment.d.ts +3 -0
- package/dist/review/actions/update-review-comment.js +28 -3
- package/dist/review/errors.d.ts +33 -0
- package/dist/review/errors.js +51 -0
- package/dist/review/index.d.ts +5 -4
- package/dist/review/index.js +3 -2
- package/dist/review/registry.d.ts +51 -1
- package/dist/review/registry.js +117 -0
- package/dist/review/store.d.ts +59 -14
- package/dist/review/store.js +622 -101
- package/dist/review/suggestions/actions.js +65 -10
- package/dist/review/suggestions/actor-kind.d.ts +5 -0
- package/dist/review/suggestions/actor-kind.js +20 -0
- package/dist/review/suggestions/store.js +16 -2
- package/dist/review/suggestions/types.d.ts +13 -0
- package/dist/review/types.d.ts +89 -0
- package/dist/scripts/db/dev-query-proxy.d.ts +2 -0
- package/dist/scripts/db/dev-query-proxy.js +4 -1
- package/dist/scripts/db/query.js +3 -0
- package/dist/scripts/dev/shell.js +4 -0
- package/dist/scripts/dev-session.d.ts +31 -0
- package/dist/scripts/dev-session.js +58 -0
- package/dist/scripts/resources/list.js +53 -4
- package/dist/scripts/runner.js +22 -4
- package/dist/search/query.d.ts +11 -5
- package/dist/search/query.js +52 -15
- package/dist/search/tokenize.d.ts +1 -0
- package/dist/search/tokenize.js +3 -0
- package/dist/secrets/api-keys.d.ts +4 -0
- package/dist/secrets/api-keys.js +5 -0
- package/dist/secrets/register.d.ts +2 -0
- package/dist/secrets/routes.d.ts +2 -2
- package/dist/secrets/routes.js +85 -6
- package/dist/secrets/storage.d.ts +2 -0
- package/dist/secrets/storage.js +34 -9
- package/dist/secrets/substitution.d.ts +3 -2
- package/dist/secrets/substitution.js +56 -7
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/action-routes.js +8 -6
- package/dist/server/agent-chat/prompt-resources.js +117 -79
- package/dist/server/agent-chat/script-entries.js +15 -8
- package/dist/server/agent-chat-plugin.js +9 -4
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/agents-bundle.d.ts +3 -1
- package/dist/server/agents-bundle.js +4 -4
- package/dist/server/csrf.js +1 -0
- package/dist/server/dev-action-bridge.d.ts +2 -0
- package/dist/server/dev-action-bridge.js +34 -5
- package/dist/server/first-touch-route.d.ts +1 -1
- package/dist/server/http-response-telemetry.js +4 -0
- package/dist/server/loopback.js +3 -1
- package/dist/server/request-context.d.ts +26 -0
- package/dist/shared/app-state-routes.d.ts +7 -0
- package/dist/shared/app-state-routes.js +7 -0
- package/dist/shared/mcp-client-connections.d.ts +41 -0
- package/dist/shared/mcp-client-connections.js +18 -0
- package/dist/shared/mcp-connect-content.d.ts +18 -0
- package/dist/shared/mcp-connect-content.js +200 -172
- package/dist/shared/runtime-config.js +5 -1
- package/dist/templates/chat/.agents/skills/context-awareness/SKILL.md +13 -0
- package/dist/templates/default/.agents/skills/context-awareness/SKILL.md +13 -0
- package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +13 -0
- package/dist/tracking/providers.d.ts +7 -0
- package/dist/tracking/providers.js +39 -17
- package/dist/triggers/condition-evaluator.d.ts +2 -0
- package/dist/triggers/condition-evaluator.js +93 -3
- package/dist/triggers/dispatcher.js +24 -6
- package/docs/AGENTS.md +16 -16
- package/docs/content/create-with-an-agent.mdx +10 -4
- package/docs/content/durable-resume.mdx +11 -2
- package/docs/content/toolkit-comments-review.mdx +54 -1
- package/package.json +5 -4
- package/src/templates/chat/.agents/skills/context-awareness/SKILL.md +13 -0
- package/src/templates/default/.agents/skills/context-awareness/SKILL.md +13 -0
- package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +13 -0
|
@@ -2,9 +2,9 @@ import { z } from "zod";
|
|
|
2
2
|
import { defineAction, fail } from "../../action.js";
|
|
3
3
|
import { getDbExec } from "../../db/client.js";
|
|
4
4
|
import { notifyReviewComment } from "../notifications.js";
|
|
5
|
-
import { assertReviewableResourceAccess } from "../registry.js";
|
|
5
|
+
import { assertReviewableResourceAccess, REVIEW_SUGGESTION_ANCHOR_KIND, reviewSuggestionAuthorRole, } from "../registry.js";
|
|
6
6
|
import { ensureReviewTables, insertReviewCommentWithClient, resolveReviewThreadWithClient, } from "../store.js";
|
|
7
|
-
import { suggestionActorKind, suggestionActorKindMatchesReceipt, } from "./actor-kind.js";
|
|
7
|
+
import { suggestionActorKind, suggestionActorKindMatchesReceipt, suggestionAuthorClient, suggestionAuthorSource, } from "./actor-kind.js";
|
|
8
8
|
import { getSuggestionAdapter } from "./registry.js";
|
|
9
9
|
import { getSuggestion, getSuggestions, ensureSuggestionTables, insertSuggestion, listSuggestions, recordDecision, getDecision, getSuggestionByCreationKey, recordSuggestionCreation, updateSuggestionStatus, getSuggestionAmendment, amendSuggestion, deleteUnclaimedSuggestion, getSuggestionProposal, insertSuggestionProposal, getProposalCreation, recordProposalCreation, getProposalDecision, recordProposalDecision, } from "./store.js";
|
|
10
10
|
const base = { resourceType: z.string().min(1), resourceId: z.string().min(1) };
|
|
@@ -108,6 +108,20 @@ async function supersedeSuggestions(tx, args, authorEmail, replacementId) {
|
|
|
108
108
|
await resolveReviewThreadWithClient(tx, prior.threadId, authorEmail, { resourceType: prior.resourceType, resourceId: prior.resourceId }, "superseded");
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
// So a suggestion's change event reaches everyone who can read the resource,
|
|
112
|
+
// not only the actor: their open page shows it without a reload.
|
|
113
|
+
function suggestionChangeResource(suggestion) {
|
|
114
|
+
return suggestion
|
|
115
|
+
? {
|
|
116
|
+
resourceType: suggestion.resourceType,
|
|
117
|
+
resourceId: suggestion.resourceId,
|
|
118
|
+
}
|
|
119
|
+
: null;
|
|
120
|
+
}
|
|
121
|
+
// A failed call reaches changeResource with no result, whatever its type says.
|
|
122
|
+
function decisionChangeResource(result) {
|
|
123
|
+
return suggestionChangeResource(result?.suggestion ?? result?.suggestions?.[0]);
|
|
124
|
+
}
|
|
111
125
|
export const createResourceSuggestion = defineAction({
|
|
112
126
|
description: "Create a typed pending suggestion without changing the canonical resource. Pass supersedes to replace your own earlier pending suggestions on the same resource in the same step.",
|
|
113
127
|
schema: z.object({
|
|
@@ -135,8 +149,12 @@ export const createResourceSuggestion = defineAction({
|
|
|
135
149
|
openWorldHint: true,
|
|
136
150
|
idempotentHint: true,
|
|
137
151
|
},
|
|
152
|
+
changeResource: ({ resourceType, resourceId }) => ({
|
|
153
|
+
resourceType,
|
|
154
|
+
resourceId,
|
|
155
|
+
}),
|
|
138
156
|
run: async (args, ctx) => {
|
|
139
|
-
const access = await assertReviewableResourceAccess(args.resourceType, args.resourceId, ctx,
|
|
157
|
+
const access = await assertReviewableResourceAccess(args.resourceType, args.resourceId, ctx, reviewSuggestionAuthorRole(args.resourceType));
|
|
140
158
|
const adapter = getSuggestionAdapter(args.adapterKind);
|
|
141
159
|
if (!adapter)
|
|
142
160
|
throw new Error("Suggestion adapter not registered");
|
|
@@ -172,6 +190,8 @@ export const createResourceSuggestion = defineAction({
|
|
|
172
190
|
threadId: `suggestion-thread-${globalThis.crypto.randomUUID()}`,
|
|
173
191
|
authorEmail,
|
|
174
192
|
actorKind,
|
|
193
|
+
authorSource: suggestionAuthorSource(ctx),
|
|
194
|
+
authorClient: suggestionAuthorClient(ctx),
|
|
175
195
|
baseRevision: args.baseRevision,
|
|
176
196
|
status: "pending",
|
|
177
197
|
summary: args.summary,
|
|
@@ -197,6 +217,7 @@ export const createResourceSuggestion = defineAction({
|
|
|
197
217
|
targetId: created.id,
|
|
198
218
|
kind: "correction",
|
|
199
219
|
anchor: created.operations.map((item) => item.anchor ?? null),
|
|
220
|
+
anchorKind: REVIEW_SUGGESTION_ANCHOR_KIND,
|
|
200
221
|
body: created.summary,
|
|
201
222
|
authorEmail: created.authorEmail,
|
|
202
223
|
createdBy: actorKind,
|
|
@@ -261,7 +282,7 @@ export const updateResourceSuggestion = defineAction({
|
|
|
261
282
|
const initial = await getSuggestion(args.id);
|
|
262
283
|
if (!initial)
|
|
263
284
|
fail("Suggestion not found", { statusCode: 404, errorCode: "not_found" });
|
|
264
|
-
await assertReviewableResourceAccess(initial.resourceType, initial.resourceId, ctx,
|
|
285
|
+
await assertReviewableResourceAccess(initial.resourceType, initial.resourceId, ctx, reviewSuggestionAuthorRole(initial.resourceType));
|
|
265
286
|
const author = ctx?.userEmail;
|
|
266
287
|
if (!author || author !== initial.authorEmail) {
|
|
267
288
|
fail("Only the author can amend this suggestion", {
|
|
@@ -285,7 +306,7 @@ export const updateResourceSuggestion = defineAction({
|
|
|
285
306
|
statusCode: 404,
|
|
286
307
|
errorCode: "not_found",
|
|
287
308
|
});
|
|
288
|
-
const access = await assertReviewableResourceAccess(current.resourceType, current.resourceId, { ...ctx, transaction: tx },
|
|
309
|
+
const access = await assertReviewableResourceAccess(current.resourceType, current.resourceId, { ...ctx, transaction: tx }, reviewSuggestionAuthorRole(current.resourceType));
|
|
289
310
|
if (current.authorEmail !== author)
|
|
290
311
|
fail("Only the author can amend this suggestion", {
|
|
291
312
|
statusCode: 403,
|
|
@@ -339,6 +360,7 @@ export const updateResourceSuggestion = defineAction({
|
|
|
339
360
|
};
|
|
340
361
|
},
|
|
341
362
|
},
|
|
363
|
+
changeResource: (_input, result) => suggestionChangeResource(result),
|
|
342
364
|
});
|
|
343
365
|
export const listResourceSuggestions = defineAction({
|
|
344
366
|
description: "List typed suggestions for a resource.",
|
|
@@ -394,7 +416,7 @@ function decisionAccessRole(suggestion, decision, ctx) {
|
|
|
394
416
|
errorCode: "forbidden",
|
|
395
417
|
});
|
|
396
418
|
}
|
|
397
|
-
return
|
|
419
|
+
return reviewSuggestionAuthorRole(suggestion.resourceType);
|
|
398
420
|
}
|
|
399
421
|
export const decideResourceSuggestion = defineAction({
|
|
400
422
|
description: "Accept or reject a pending suggestion atomically, which needs edit access. An accept that can no longer be placed on the current resource fails with 409 `suggestion_stale`, changes nothing, and leaves the suggestion pending. Its author may instead withdraw it with comment access; a withdrawn suggestion was never reviewed and leaves the resource unchanged.",
|
|
@@ -551,6 +573,7 @@ export const decideResourceSuggestion = defineAction({
|
|
|
551
573
|
: undefined;
|
|
552
574
|
},
|
|
553
575
|
},
|
|
576
|
+
changeResource: (_input, result) => decisionChangeResource(result),
|
|
554
577
|
});
|
|
555
578
|
const proposalCreationSchema = z.object({
|
|
556
579
|
...base,
|
|
@@ -611,8 +634,12 @@ export const createResourceSuggestionProposal = defineAction({
|
|
|
611
634
|
openWorldHint: true,
|
|
612
635
|
idempotentHint: true,
|
|
613
636
|
},
|
|
637
|
+
changeResource: ({ resourceType, resourceId }) => ({
|
|
638
|
+
resourceType,
|
|
639
|
+
resourceId,
|
|
640
|
+
}),
|
|
614
641
|
run: async (args, ctx) => {
|
|
615
|
-
await assertReviewableResourceAccess(args.resourceType, args.resourceId, ctx,
|
|
642
|
+
await assertReviewableResourceAccess(args.resourceType, args.resourceId, ctx, reviewSuggestionAuthorRole(args.resourceType));
|
|
616
643
|
const adapter = getSuggestionAdapter(args.adapterKind);
|
|
617
644
|
if (!adapter)
|
|
618
645
|
throw new Error("Suggestion adapter not registered");
|
|
@@ -634,7 +661,7 @@ export const createResourceSuggestionProposal = defineAction({
|
|
|
634
661
|
if (!db.transaction)
|
|
635
662
|
throw new Error("Proposal creation requires an atomic database transaction");
|
|
636
663
|
const replay = async (tx, prior) => {
|
|
637
|
-
await assertReviewableResourceAccess(args.resourceType, args.resourceId, { ...ctx, transaction: tx },
|
|
664
|
+
await assertReviewableResourceAccess(args.resourceType, args.resourceId, { ...ctx, transaction: tx }, reviewSuggestionAuthorRole(args.resourceType));
|
|
638
665
|
if (prior.requestHash !== requestHash ||
|
|
639
666
|
prior.authorEmail !== authorEmail ||
|
|
640
667
|
prior.actorKind !== actorKind) {
|
|
@@ -664,7 +691,7 @@ export const createResourceSuggestionProposal = defineAction({
|
|
|
664
691
|
const prior = await getProposalCreation(tx, args.idempotencyKey);
|
|
665
692
|
if (prior)
|
|
666
693
|
return replay(tx, prior);
|
|
667
|
-
const access = await assertReviewableResourceAccess(args.resourceType, args.resourceId, { ...ctx, transaction: tx },
|
|
694
|
+
const access = await assertReviewableResourceAccess(args.resourceType, args.resourceId, { ...ctx, transaction: tx }, reviewSuggestionAuthorRole(args.resourceType));
|
|
668
695
|
const proposal = args.proposalId
|
|
669
696
|
? await getSuggestionProposal(tx, args.proposalId)
|
|
670
697
|
: await insertSuggestionProposal(tx, {
|
|
@@ -675,11 +702,35 @@ export const createResourceSuggestionProposal = defineAction({
|
|
|
675
702
|
authorEmail,
|
|
676
703
|
actorKind,
|
|
677
704
|
});
|
|
678
|
-
if (!proposal)
|
|
705
|
+
if (!proposal) {
|
|
706
|
+
// A first call returns both a suggestion ID and a proposal ID, and
|
|
707
|
+
// agents appending a second edit often pass the suggestion's. Name
|
|
708
|
+
// the proposal when that suggestion is the caller's own, on this
|
|
709
|
+
// same resource: only its author can append to it.
|
|
710
|
+
const suggestion = args.proposalId
|
|
711
|
+
? await getSuggestion(args.proposalId, tx)
|
|
712
|
+
: null;
|
|
713
|
+
if (suggestion &&
|
|
714
|
+
suggestion.resourceType === args.resourceType &&
|
|
715
|
+
suggestion.resourceId === args.resourceId &&
|
|
716
|
+
suggestion.authorEmail === authorEmail &&
|
|
717
|
+
suggestion.actorKind === actorKind) {
|
|
718
|
+
fail(suggestion.proposalId
|
|
719
|
+
? `${suggestion.id} is a suggestion ID, not a proposal ID. To add this edit to the same review, pass its proposal ID: ${suggestion.proposalId}.`
|
|
720
|
+
: `${suggestion.id} is a suggestion ID, not a proposal ID, and it belongs to no proposal. Leave the proposal ID out to start a new review.`, {
|
|
721
|
+
statusCode: 400,
|
|
722
|
+
errorCode: "suggestion_id_not_proposal_id",
|
|
723
|
+
details: {
|
|
724
|
+
suggestionId: suggestion.id,
|
|
725
|
+
proposalId: suggestion.proposalId ?? null,
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
}
|
|
679
729
|
fail("Proposal not found", {
|
|
680
730
|
statusCode: 404,
|
|
681
731
|
errorCode: "not_found",
|
|
682
732
|
});
|
|
733
|
+
}
|
|
683
734
|
if (proposal.resourceType !== args.resourceType ||
|
|
684
735
|
proposal.resourceId !== args.resourceId ||
|
|
685
736
|
proposal.adapterKind !== args.adapterKind ||
|
|
@@ -716,6 +767,8 @@ export const createResourceSuggestionProposal = defineAction({
|
|
|
716
767
|
threadId: `suggestion-thread-${globalThis.crypto.randomUUID()}`,
|
|
717
768
|
authorEmail,
|
|
718
769
|
actorKind,
|
|
770
|
+
authorSource: suggestionAuthorSource(ctx),
|
|
771
|
+
authorClient: suggestionAuthorClient(ctx),
|
|
719
772
|
baseRevision: args.baseRevision,
|
|
720
773
|
status: "pending",
|
|
721
774
|
summary: item.summary,
|
|
@@ -733,6 +786,7 @@ export const createResourceSuggestionProposal = defineAction({
|
|
|
733
786
|
targetId: created.id,
|
|
734
787
|
kind: "correction",
|
|
735
788
|
anchor: created.operations.map((part) => part.anchor ?? null),
|
|
789
|
+
anchorKind: REVIEW_SUGGESTION_ANCHOR_KIND,
|
|
736
790
|
body: created.summary,
|
|
737
791
|
authorEmail,
|
|
738
792
|
createdBy: actorKind,
|
|
@@ -998,4 +1052,5 @@ export const decideResourceSuggestionProposal = defineAction({
|
|
|
998
1052
|
: undefined;
|
|
999
1053
|
},
|
|
1000
1054
|
},
|
|
1055
|
+
changeResource: (_input, result) => decisionChangeResource(result),
|
|
1001
1056
|
});
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type { SuggestionAuthorSource } from "./types.js";
|
|
1
2
|
export declare function suggestionActorKind(ctx: unknown): "agent" | "human" | "system";
|
|
3
|
+
/** How an agent's call arrived, or null for a person's. */
|
|
4
|
+
export declare function suggestionAuthorSource(ctx: unknown): SuggestionAuthorSource | null;
|
|
5
|
+
/** The MCP host an MCP call's client said it was, such as `claude-code`. */
|
|
6
|
+
export declare function suggestionAuthorClient(ctx: unknown): string | null;
|
|
2
7
|
export declare const LEGACY_SUGGESTION_RECEIPT_VERSION = 1;
|
|
3
8
|
export declare function suggestionActorKindMatchesReceipt(receiptActorKind: string | null | undefined, actorKind: string, receiptVersion: number | null | undefined): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveMcpClientHostId } from "../../shared/mcp-connect-content.js";
|
|
1
2
|
export function suggestionActorKind(ctx) {
|
|
2
3
|
const agentCallers = new Set(["agent", "tool", "mcp", "webmcp", "a2a"]);
|
|
3
4
|
const caller = ctx?.caller;
|
|
@@ -5,6 +6,25 @@ export function suggestionActorKind(ctx) {
|
|
|
5
6
|
return "agent";
|
|
6
7
|
return ctx?.userEmail ? "human" : "system";
|
|
7
8
|
}
|
|
9
|
+
/** How an agent's call arrived, or null for a person's. */
|
|
10
|
+
export function suggestionAuthorSource(ctx) {
|
|
11
|
+
const caller = ctx?.caller;
|
|
12
|
+
if (caller === "mcp" || caller === "webmcp")
|
|
13
|
+
return "mcp";
|
|
14
|
+
if (caller === "a2a")
|
|
15
|
+
return "a2a";
|
|
16
|
+
if (caller === "agent" || caller === "tool")
|
|
17
|
+
return "agent";
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/** The MCP host an MCP call's client said it was, such as `claude-code`. */
|
|
21
|
+
export function suggestionAuthorClient(ctx) {
|
|
22
|
+
if (suggestionAuthorSource(ctx) !== "mcp")
|
|
23
|
+
return null;
|
|
24
|
+
// An MCP caller means ctx is a context object.
|
|
25
|
+
const client = ctx.mcpClient;
|
|
26
|
+
return resolveMcpClientHostId(client) ?? null;
|
|
27
|
+
}
|
|
8
28
|
export const LEGACY_SUGGESTION_RECEIPT_VERSION = 1;
|
|
9
29
|
export function suggestionActorKindMatchesReceipt(receiptActorKind, actorKind, receiptVersion) {
|
|
10
30
|
if (receiptActorKind === actorKind)
|
|
@@ -69,6 +69,14 @@ export const reviewSuggestionsStore = defineStore({
|
|
|
69
69
|
await exec.execute("CREATE INDEX IF NOT EXISTS idx_review_suggestion_proposal_members ON agent_review_suggestions (proposal_id, created_at)");
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
name: "author-source-and-client",
|
|
74
|
+
run: async () => {
|
|
75
|
+
for (const column of ["author_source", "author_client"]) {
|
|
76
|
+
await ensureColumnExists("agent_review_suggestions", column, `ALTER TABLE agent_review_suggestions ADD COLUMN IF NOT EXISTS ${column} TEXT`);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
},
|
|
72
80
|
],
|
|
73
81
|
});
|
|
74
82
|
export function ensureSuggestionTables(client = getDbExec()) {
|
|
@@ -143,7 +151,7 @@ export async function insertSuggestion(input, client = getDbExec()) {
|
|
|
143
151
|
const suggestionId = `suggestion-${newId()}`;
|
|
144
152
|
const now = new Date().toISOString();
|
|
145
153
|
await client.execute({
|
|
146
|
-
sql: "INSERT INTO agent_review_suggestions (id,resource_type,resource_id,adapter_kind,adapter_version,thread_id,author_email,actor_kind,base_revision,status,summary,owner_email,org_id,visibility,created_at,updated_at,metadata_json,proposal_id) VALUES (
|
|
154
|
+
sql: "INSERT INTO agent_review_suggestions (id,resource_type,resource_id,adapter_kind,adapter_version,thread_id,author_email,actor_kind,author_source,author_client,base_revision,status,summary,owner_email,org_id,visibility,created_at,updated_at,metadata_json,proposal_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
147
155
|
args: [
|
|
148
156
|
suggestionId,
|
|
149
157
|
input.resourceType,
|
|
@@ -153,6 +161,8 @@ export async function insertSuggestion(input, client = getDbExec()) {
|
|
|
153
161
|
input.threadId,
|
|
154
162
|
input.authorEmail,
|
|
155
163
|
input.actorKind,
|
|
164
|
+
input.authorSource ?? null,
|
|
165
|
+
input.authorClient ?? null,
|
|
156
166
|
input.baseRevision,
|
|
157
167
|
input.status,
|
|
158
168
|
input.summary,
|
|
@@ -307,6 +317,9 @@ function suggestionFromRows(row, operationRows) {
|
|
|
307
317
|
threadId: String(row.thread_id),
|
|
308
318
|
authorEmail: row.author_email,
|
|
309
319
|
actorKind: row.actor_kind,
|
|
320
|
+
authorSource: (row.author_source ??
|
|
321
|
+
null),
|
|
322
|
+
authorClient: (row.author_client ?? null),
|
|
310
323
|
baseRevision: String(row.base_revision),
|
|
311
324
|
status: row.status,
|
|
312
325
|
summary: String(row.summary),
|
|
@@ -336,8 +349,9 @@ export async function listSuggestions(resourceType, resourceId, statuses, client
|
|
|
336
349
|
? ` AND s.status IN (${statuses.map(() => "?").join(",")})`
|
|
337
350
|
: "";
|
|
338
351
|
args.push(...(statuses ?? []));
|
|
352
|
+
// raw-sql-ok: the filter adds only ? placeholders, one per bound status.
|
|
339
353
|
const rows = (await client.execute({
|
|
340
|
-
sql: `SELECT s.id AS suggestion_id,s.proposal_id,p.summary AS proposal_summary,s.revision,s.resource_type,s.resource_id,s.adapter_kind,s.adapter_version,s.thread_id,s.author_email,s.actor_kind,s.base_revision,s.status,s.summary,s.owner_email,s.org_id,s.visibility,s.created_at,s.updated_at,s.metadata_json,o.id AS operation_id,o.ordinal AS operation_ordinal,o.operation_kind,o.target_id,o.before_json,o.after_json,o.anchor_json,o.dependencies_json,o.schema_version FROM agent_review_suggestions s LEFT JOIN agent_review_suggestion_proposals p ON p.id = s.proposal_id LEFT JOIN agent_review_suggestion_operations o ON o.suggestion_id = s.id WHERE s.resource_type = ? AND s.resource_id = ?${filter} ORDER BY s.created_at,o.ordinal`,
|
|
354
|
+
sql: `SELECT s.id AS suggestion_id,s.proposal_id,p.summary AS proposal_summary,s.revision,s.resource_type,s.resource_id,s.adapter_kind,s.adapter_version,s.thread_id,s.author_email,s.actor_kind,s.author_source,s.author_client,s.base_revision,s.status,s.summary,s.owner_email,s.org_id,s.visibility,s.created_at,s.updated_at,s.metadata_json,o.id AS operation_id,o.ordinal AS operation_ordinal,o.operation_kind,o.target_id,o.before_json,o.after_json,o.anchor_json,o.dependencies_json,o.schema_version FROM agent_review_suggestions s LEFT JOIN agent_review_suggestion_proposals p ON p.id = s.proposal_id LEFT JOIN agent_review_suggestion_operations o ON o.suggestion_id = s.id WHERE s.resource_type = ? AND s.resource_id = ?${filter} ORDER BY s.created_at,o.ordinal`,
|
|
341
355
|
args,
|
|
342
356
|
})).rows;
|
|
343
357
|
const grouped = new Map();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Visibility } from "../../sharing/schema.js";
|
|
2
2
|
export type SuggestionStatus = "pending" | "accepted" | "rejected" | "stale" | "superseded" | "withdrawn";
|
|
3
3
|
export type SuggestionDecision = "accepted" | "rejected";
|
|
4
|
+
export type SuggestionAuthorSource = "mcp" | "a2a" | "agent";
|
|
4
5
|
export interface SuggestionOperation {
|
|
5
6
|
id?: string;
|
|
6
7
|
ordinal: number;
|
|
@@ -24,6 +25,18 @@ export interface ResourceSuggestion {
|
|
|
24
25
|
threadId: string;
|
|
25
26
|
authorEmail: string | null;
|
|
26
27
|
actorKind: "human" | "agent" | "system";
|
|
28
|
+
/**
|
|
29
|
+
* How an agent's suggestion arrived: over MCP, from another app's agent
|
|
30
|
+
* (`a2a`), or from the app's own agent. Null for people and for
|
|
31
|
+
* suggestions made before it was recorded.
|
|
32
|
+
*/
|
|
33
|
+
authorSource?: SuggestionAuthorSource | null;
|
|
34
|
+
/**
|
|
35
|
+
* The MCP host the agent's client said it was, as a connect host id such as
|
|
36
|
+
* `claude-code` (see `mcpConnectHostLabel`). Null when the client named no
|
|
37
|
+
* host this app knows.
|
|
38
|
+
*/
|
|
39
|
+
authorClient?: string | null;
|
|
27
40
|
baseRevision: string;
|
|
28
41
|
status: SuggestionStatus;
|
|
29
42
|
summary: string;
|
package/dist/review/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DbExec } from "../db/client.js";
|
|
1
2
|
import type { Visibility } from "../sharing/schema.js";
|
|
2
3
|
export type ReviewResourceRole = "viewer" | "commenter" | "editor" | "admin" | "owner";
|
|
3
4
|
export type ReviewCommentKind = "comment" | "annotation" | "correction" | "question" | "decision" | "review";
|
|
@@ -24,6 +25,77 @@ export interface ReviewableResourceRegistration {
|
|
|
24
25
|
displayName?: string;
|
|
25
26
|
resolveUrl?: (resourceId: string) => Promise<string | null | undefined> | string | null | undefined;
|
|
26
27
|
resolveAccess?: (resourceId: string, ctx?: ReviewResourceContext) => Promise<ReviewResourceAccess | null> | ReviewResourceAccess | null;
|
|
28
|
+
/** Who may comment, and where a new thread goes. */
|
|
29
|
+
comments?: ReviewCommentPolicy;
|
|
30
|
+
/**
|
|
31
|
+
* What a comment can attach to, by anchor kind. Once a resource declares
|
|
32
|
+
* any, every anchored comment on it names one, and its anchor must pass that
|
|
33
|
+
* kind's check before anything is written.
|
|
34
|
+
*/
|
|
35
|
+
anchorKinds?: Record<string, ReviewAnchorKind>;
|
|
36
|
+
/**
|
|
37
|
+
* Runs inside the same database transaction as each comment write, with
|
|
38
|
+
* that transaction's handle. Throwing refuses the write. Use the handle for
|
|
39
|
+
* any database work here: `getDbExec()` would run on another connection.
|
|
40
|
+
*/
|
|
41
|
+
onCommentWrite?: (event: ReviewCommentWriteEvent, tx: DbExec) => Promise<void> | void;
|
|
42
|
+
}
|
|
43
|
+
export interface ReviewCommentPolicy {
|
|
44
|
+
/**
|
|
45
|
+
* The lowest role that may comment, reply and react, and edit or delete
|
|
46
|
+
* their own comments. Defaults to `commenter`.
|
|
47
|
+
*/
|
|
48
|
+
minimumRole?: ReviewResourceRole;
|
|
49
|
+
/**
|
|
50
|
+
* Where a new thread that mentions no one goes: the agent (the default) or
|
|
51
|
+
* people. A thread with a mention always goes to people.
|
|
52
|
+
*/
|
|
53
|
+
unmentionedRoute?: ReviewResolutionTarget;
|
|
54
|
+
}
|
|
55
|
+
export interface ReviewAnchorKind {
|
|
56
|
+
/** The version new anchors of this kind are written at. */
|
|
57
|
+
version: number;
|
|
58
|
+
/**
|
|
59
|
+
* Checks an anchor written at `version` and returns it as it's stored.
|
|
60
|
+
* Throw to refuse a malformed anchor; nothing is written.
|
|
61
|
+
*/
|
|
62
|
+
parse: (anchor: unknown, version: number) => unknown;
|
|
63
|
+
}
|
|
64
|
+
/** A comment write, as a resource's `onCommentWrite` hook sees it. */
|
|
65
|
+
export type ReviewCommentWriteEvent = {
|
|
66
|
+
type: "created";
|
|
67
|
+
comment: ReviewComment;
|
|
68
|
+
} | {
|
|
69
|
+
type: "edited" | "anchor-moved";
|
|
70
|
+
comment: ReviewComment;
|
|
71
|
+
/** Who changed it, which for an editor moving a pin isn't the author. */
|
|
72
|
+
actorEmail: string | null;
|
|
73
|
+
} | {
|
|
74
|
+
type: "resolved" | "reopened";
|
|
75
|
+
resourceType: string;
|
|
76
|
+
resourceId: string;
|
|
77
|
+
threadId: string;
|
|
78
|
+
actorEmail: string | null;
|
|
79
|
+
} | {
|
|
80
|
+
type: "deleted";
|
|
81
|
+
resourceType: string;
|
|
82
|
+
resourceId: string;
|
|
83
|
+
threadId: string;
|
|
84
|
+
/** The comment deleted and, for a thread's first comment, the rest. */
|
|
85
|
+
commentIds: string[];
|
|
86
|
+
actorEmail: string | null;
|
|
87
|
+
};
|
|
88
|
+
/** A comment's link to the same comment or thread in another system. */
|
|
89
|
+
export interface ReviewCommentLink {
|
|
90
|
+
/** The other system, such as `notion`. */
|
|
91
|
+
provider: string;
|
|
92
|
+
/** What the outside ID names there, such as `comment` or `discussion`. */
|
|
93
|
+
externalKind: string;
|
|
94
|
+
externalId: string;
|
|
95
|
+
commentId: string;
|
|
96
|
+
resourceType: string;
|
|
97
|
+
resourceId: string;
|
|
98
|
+
createdAt: string;
|
|
27
99
|
}
|
|
28
100
|
export interface ReviewMention {
|
|
29
101
|
label: string;
|
|
@@ -52,9 +124,26 @@ export interface ReviewComment {
|
|
|
52
124
|
threadId: string;
|
|
53
125
|
parentCommentId: string | null;
|
|
54
126
|
targetId: string | null;
|
|
127
|
+
/**
|
|
128
|
+
* The part of the resource the thread sits on, such as a slide. The kit
|
|
129
|
+
* always returns this and the anchor and revision fields below; they're
|
|
130
|
+
* optional so comments built elsewhere, such as optimistic ones, needn't.
|
|
131
|
+
*/
|
|
132
|
+
surfaceId?: string | null;
|
|
55
133
|
kind: ReviewCommentKind;
|
|
56
134
|
status: ReviewCommentStatus;
|
|
57
135
|
anchor: unknown;
|
|
136
|
+
/** The anchor's kind and the version it was written at, when typed. */
|
|
137
|
+
anchorKind?: string | null;
|
|
138
|
+
anchorVersion?: number | null;
|
|
139
|
+
/**
|
|
140
|
+
* Goes up whenever this comment changes: its text, anchor, status or
|
|
141
|
+
* deletion, and on a thread's first comment, which holds the thread's
|
|
142
|
+
* routing, its routing and agent pickup. So a write can check nothing
|
|
143
|
+
* changed under it. Notification bookkeeping leaves it alone, so a new
|
|
144
|
+
* comment's author can edit it at the revision they got back.
|
|
145
|
+
*/
|
|
146
|
+
revision?: number;
|
|
58
147
|
body: string;
|
|
59
148
|
authorEmail: string | null;
|
|
60
149
|
authorName: string | null;
|
|
@@ -5,6 +5,8 @@ export interface TryForwardDbQueryOptions {
|
|
|
5
5
|
format?: string;
|
|
6
6
|
userEmail?: string;
|
|
7
7
|
orgId?: string;
|
|
8
|
+
/** The caller acts in no org; the dev server must not look one up. */
|
|
9
|
+
orgScope?: "personal";
|
|
8
10
|
print: (rows: Record<string, unknown>[], sql: string, format?: string) => void;
|
|
9
11
|
}
|
|
10
12
|
export declare function tryForwardDbQueryToDevServer(options: TryForwardDbQueryOptions): Promise<boolean>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Agent } from "undici";
|
|
2
|
-
import { DEV_ACTION_ORG_HEADER, DEV_ACTION_TOKEN_HEADER, DEV_ACTION_USER_HEADER, DEV_DB_QUERY_ROUTE, } from "../../server/dev-action-bridge.js";
|
|
2
|
+
import { DEV_ACTION_ORG_HEADER, DEV_ACTION_ORG_SCOPE_HEADER, DEV_ACTION_TOKEN_HEADER, DEV_ACTION_USER_HEADER, DEV_DB_QUERY_ROUTE, } from "../../server/dev-action-bridge.js";
|
|
3
3
|
import { discoverPgliteDevServer } from "./dev-server-discovery.js";
|
|
4
4
|
export async function tryForwardDbQueryToDevServer(options) {
|
|
5
5
|
const discovery = discoverPgliteDevServer();
|
|
@@ -23,6 +23,9 @@ export async function tryForwardDbQueryToDevServer(options) {
|
|
|
23
23
|
? { [DEV_ACTION_USER_HEADER]: options.userEmail }
|
|
24
24
|
: {}),
|
|
25
25
|
...(options.orgId ? { [DEV_ACTION_ORG_HEADER]: options.orgId } : {}),
|
|
26
|
+
...(options.orgScope === "personal"
|
|
27
|
+
? { [DEV_ACTION_ORG_SCOPE_HEADER]: "personal" }
|
|
28
|
+
: {}),
|
|
26
29
|
},
|
|
27
30
|
body: JSON.stringify({
|
|
28
31
|
sql: options.sql,
|
package/dist/scripts/db/query.js
CHANGED
|
@@ -125,6 +125,9 @@ Options:
|
|
|
125
125
|
format: parsed.format,
|
|
126
126
|
userEmail: getRequestUserEmail(),
|
|
127
127
|
orgId: getRequestOrgId() ?? undefined,
|
|
128
|
+
// The CLI already resolved this call's org; with none, it is personal,
|
|
129
|
+
// so the dev server must not look one up again.
|
|
130
|
+
...(getRequestOrgId() ? {} : { orgScope: "personal" }),
|
|
128
131
|
print: printTable,
|
|
129
132
|
});
|
|
130
133
|
if (forwarded)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getRequestOrgId, getRequestTimezone, getRequestUserEmail, getRequestUserName, hasRequestContext, } from "../../server/request-context.js";
|
|
4
|
+
import { AGENT_ORG_SCOPE_ENV } from "../dev-session.js";
|
|
4
5
|
import { parseArgs } from "../utils.js";
|
|
5
6
|
const MAX_OUTPUT = 50_000;
|
|
6
7
|
const TIMEOUT_MS = 30_000;
|
|
@@ -66,6 +67,9 @@ function childProcessEnvForRequest() {
|
|
|
66
67
|
setOrClearRequestEnv(env, "AGENT_USER_EMAIL", userEmail, requestScoped);
|
|
67
68
|
setOrClearRequestEnv(env, "AGENT_USER_NAME", userName, requestScoped);
|
|
68
69
|
setOrClearRequestEnv(env, "AGENT_ORG_ID", orgId, requestScoped);
|
|
70
|
+
// A request with no org runs its commands with no org too. Without this, a
|
|
71
|
+
// `pnpm action` it runs would look up the user's active org instead.
|
|
72
|
+
setOrClearRequestEnv(env, AGENT_ORG_SCOPE_ENV, requestScoped && !orgId ? "personal" : undefined, requestScoped);
|
|
69
73
|
setOrClearRequestEnv(env, "AGENT_USER_TIMEZONE", timezone, requestScoped);
|
|
70
74
|
return env;
|
|
71
75
|
}
|
|
@@ -7,3 +7,34 @@
|
|
|
7
7
|
* (sign in via the running app, or set `AGENT_USER_EMAIL`).
|
|
8
8
|
*/
|
|
9
9
|
export declare function resolveDevUserEmail(): Promise<string | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* Set to "personal" on a child process whose caller acts in no org, so a
|
|
12
|
+
* `pnpm action` it runs stays out of the user's orgs too. The agent's shell
|
|
13
|
+
* tool sets it for a request with no org.
|
|
14
|
+
*/
|
|
15
|
+
export declare const AGENT_ORG_SCOPE_ENV = "AGENT_ORG_SCOPE";
|
|
16
|
+
export interface DevOrgChoice {
|
|
17
|
+
/** An org the caller named: `AGENT_ORG_ID` or a forwarded org header. */
|
|
18
|
+
orgId?: string | undefined;
|
|
19
|
+
/** The caller acts in no org and must not be given one. */
|
|
20
|
+
personal?: boolean | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** The org choice this CLI process inherited from its environment. */
|
|
23
|
+
export declare function devOrgChoiceFromEnv(env?: NodeJS.ProcessEnv): DevOrgChoice;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the org scope a local CLI call acts in, for `userEmail`, as fields
|
|
26
|
+
* for its request context.
|
|
27
|
+
*
|
|
28
|
+
* An inherited personal scope wins, then a named org. Otherwise this is the
|
|
29
|
+
* org the signed-in app uses for the same user: their active org, else their
|
|
30
|
+
* first membership, or none when they chose their personal space or have no
|
|
31
|
+
* org. Without it, `pnpm action` ran with no org and saw only the user's
|
|
32
|
+
* personal rows while the app showed their org's.
|
|
33
|
+
*
|
|
34
|
+
* Like the app, a membership table that can't be read counts as no org;
|
|
35
|
+
* transient database errors still throw.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveDevOrgScope(userEmail: string | undefined, choice: DevOrgChoice): Promise<{
|
|
38
|
+
orgId?: string;
|
|
39
|
+
orgScope?: "personal";
|
|
40
|
+
}>;
|
|
@@ -52,3 +52,61 @@ export async function resolveDevUserEmail() {
|
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Set to "personal" on a child process whose caller acts in no org, so a
|
|
57
|
+
* `pnpm action` it runs stays out of the user's orgs too. The agent's shell
|
|
58
|
+
* tool sets it for a request with no org.
|
|
59
|
+
*/
|
|
60
|
+
export const AGENT_ORG_SCOPE_ENV = "AGENT_ORG_SCOPE";
|
|
61
|
+
/** The org choice this CLI process inherited from its environment. */
|
|
62
|
+
export function devOrgChoiceFromEnv(env = process.env) {
|
|
63
|
+
return {
|
|
64
|
+
orgId: env.AGENT_ORG_ID || undefined,
|
|
65
|
+
personal: env[AGENT_ORG_SCOPE_ENV] === "personal",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the org scope a local CLI call acts in, for `userEmail`, as fields
|
|
70
|
+
* for its request context.
|
|
71
|
+
*
|
|
72
|
+
* An inherited personal scope wins, then a named org. Otherwise this is the
|
|
73
|
+
* org the signed-in app uses for the same user: their active org, else their
|
|
74
|
+
* first membership, or none when they chose their personal space or have no
|
|
75
|
+
* org. Without it, `pnpm action` ran with no org and saw only the user's
|
|
76
|
+
* personal rows while the app showed their org's.
|
|
77
|
+
*
|
|
78
|
+
* Like the app, a membership table that can't be read counts as no org;
|
|
79
|
+
* transient database errors still throw.
|
|
80
|
+
*/
|
|
81
|
+
export async function resolveDevOrgScope(userEmail, choice) {
|
|
82
|
+
// Personal comes first. Only the agent's shell sets it, for a request with
|
|
83
|
+
// no org, and the child's own env loading can put an AGENT_ORG_ID from
|
|
84
|
+
// `.env` back, which must not give the command an org.
|
|
85
|
+
if (choice.personal)
|
|
86
|
+
return { orgScope: "personal" };
|
|
87
|
+
if (choice.orgId)
|
|
88
|
+
return { orgId: choice.orgId };
|
|
89
|
+
if (!userEmail)
|
|
90
|
+
return {};
|
|
91
|
+
if (await localDatabaseIsMissing())
|
|
92
|
+
return {};
|
|
93
|
+
const { resolveOrgIdForEmail } = await import("../org/context.js");
|
|
94
|
+
const orgId = await resolveOrgIdForEmail(userEmail);
|
|
95
|
+
return orgId ? { orgId } : {};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* True when the app's local PGlite database hasn't been created yet. It has
|
|
99
|
+
* no orgs, and opening it to look would create it and run every migration,
|
|
100
|
+
* which takes many seconds for an action that may not touch the database.
|
|
101
|
+
*/
|
|
102
|
+
async function localDatabaseIsMissing() {
|
|
103
|
+
const { getRuntimeDatabaseUrl, isPgliteUrl, pgliteDataDirFromUrl, pgliteRuntimeDataDir, } = await import("../db/client.js");
|
|
104
|
+
const url = getRuntimeDatabaseUrl("pglite:./data/pglite");
|
|
105
|
+
if (!isPgliteUrl(url))
|
|
106
|
+
return false;
|
|
107
|
+
const dataDir = pgliteRuntimeDataDir(pgliteDataDirFromUrl(url));
|
|
108
|
+
if (dataDir === "memory://")
|
|
109
|
+
return false;
|
|
110
|
+
const fs = await import("node:fs");
|
|
111
|
+
return !fs.existsSync(dataDir);
|
|
112
|
+
}
|