Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @openagentpack/server

## 0.0.12

### Patch Changes

- Updated dependencies [fd62da0]
- @openagentpack/sdk@0.7.2

## 0.0.11

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openagentpack/server",
"version": "0.0.11",
"version": "0.0.12",
"private": true,
"license": "Apache-2.0",
"exports": {
Expand All @@ -18,7 +18,7 @@
"@hono/zod-openapi": "^1.4.0",
"@openagentpack/sdk": "workspace:*",
"chokidar": "^4.0.3",
"hono": "^4.12.28",
"hono": "^4.13.5",
"yaml": "^2.9.0"
},
"devDependencies": {
Expand Down
16 changes: 8 additions & 8 deletions apps/webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
"license": "Apache-2.0",
"description": "",
"dependencies": {
"@tiptap/core": "3.30.4",
"@tiptap/extension-document": "3.30.4",
"@tiptap/extension-mention": "3.30.4",
"@tiptap/extension-paragraph": "3.30.4",
"@tiptap/extension-text": "3.30.4",
"@tiptap/pm": "3.30.4",
"@tiptap/react": "3.30.4",
"@tiptap/suggestion": "3.30.4",
"@tiptap/core": "3.30.5",
"@tiptap/extension-document": "3.30.5",
"@tiptap/extension-mention": "3.30.5",
"@tiptap/extension-paragraph": "3.30.5",
"@tiptap/extension-text": "3.30.5",
"@tiptap/pm": "3.30.5",
"@tiptap/react": "3.30.5",
"@tiptap/suggestion": "3.30.5",
"i18next": "^25.5.2",
"lucide-react": "^1.18.0",
"react": "^19.2.7",
Expand Down
1,020 changes: 510 additions & 510 deletions bun.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,17 @@
"@changesets/cli": "^2.31.0",
"@types/bun": "^1.3.14",
"dependency-cruiser": "^17.4.3",
"js-yaml": "4.3.1",
"js-yaml": "4.3.2",
"tsup": "^8.5.1",
"typescript": "^6.0.3"
},
"overrides": {
"@tiptap/extension-bubble-menu": "3.30.4",
"@tiptap/extension-floating-menu": "3.30.4",
"@tiptap/extension-bubble-menu": "3.30.5",
"@tiptap/extension-floating-menu": "3.30.5",
"brace-expansion": "5.0.9",
"esbuild": "0.28.1",
"fast-equals": "5.3.3",
"js-yaml": "4.3.1",
"js-yaml": "4.3.2",
"nanoid": "3.3.18",
"postcss": "8.5.23",
"prosemirror-model": "1.25.11"
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @openagentpack/cli

## 0.7.2

### Patch Changes

- fd62da0: Fix Qoder managed resource identity matching during drift detection by passing declarations to comparable reads and matching on protected metadata.
- Updated dependencies [fd62da0]
- @openagentpack/sdk@0.7.2

## 0.7.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openagentpack/cli",
"version": "0.7.1",
"version": "0.7.2",
"description": "Open Agent Pack — Declaratively manage AI agent infrastructure",
"license": "Apache-2.0",
"keywords": [
Expand Down
8 changes: 8 additions & 0 deletions packages/playground/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @openagentpack/playground

## 0.7.2

### Patch Changes

- fd62da0: Fix Qoder managed resource identity matching during drift detection by passing declarations to comparable reads and matching on protected metadata.
- Updated dependencies [fd62da0]
- @openagentpack/sdk@0.7.2

## 0.7.1

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openagentpack/playground",
"version": "0.7.1",
"version": "0.7.2",
"description": "OpenAgentPack Playground — one-command local web UI for OpenAgentPack",
"license": "Apache-2.0",
"keywords": [
Expand Down Expand Up @@ -44,7 +44,7 @@
"@hono/zod-openapi": "^1.4.0",
"@openagentpack/sdk": "workspace:*",
"chokidar": "^4.0.3",
"hono": "^4.12.28",
"hono": "^4.13.5",
"yaml": "^2.9.0",
"zod": "^4.4.3"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @openagentpack/sdk

## 0.7.2

### Patch Changes

- fd62da0: Fix Qoder managed resource identity matching during drift detection by passing declarations to comparable reads and matching on protected metadata.

## 0.7.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openagentpack/sdk",
"version": "0.7.1",
"version": "0.7.2",
"description": "OpenAgentPack SDK (Node-compatible runtime)",
"license": "Apache-2.0",
"keywords": [
Expand Down
10 changes: 9 additions & 1 deletion packages/sdk/src/internal/core/resource-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,15 @@ export async function importResource(
// comparable and report a one-time phantom "Remote drift detected" — most
// visibly for external-reference environments OpenCMA never configured.
const provider = ctx.providers.get(address.provider);
const remote = provider ? await readComparableIfSupported(provider, address.type, remoteId, address.name) : null;
const remote = provider
? await readComparableIfSupported(
provider,
address.type,
remoteId,
address.name,
getResourceDeclaration(address, ctx.config) ?? undefined,
)
: null;
const remoteHash = remote ? stableContentHash(remote.comparable) : undefined;

const resource: ResourceState = {
Expand Down
8 changes: 7 additions & 1 deletion packages/sdk/src/internal/executor/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,13 @@ async function executeActionInner(
// hash avoids false "Remote drift detected" on the next plan.
let remoteHash = comparableHash;
let remoteSnapshot: unknown;
const remote = await readComparableIfSupported(provider, type, result.id, name);
const remote = await readComparableIfSupported(
provider,
type,
result.id,
name,
getResourceDeclaration(address, ctx.config) ?? undefined,
);
if (remote) {
remoteHash = contentHash(remote.comparable);
remoteSnapshot = remote.snapshot ?? remote.comparable;
Expand Down
9 changes: 7 additions & 2 deletions packages/sdk/src/internal/planner/refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ export async function refreshState(
const support = provider.getDriftSupport?.(res.address.type) ?? "existence";

if (supportsFullDrift(provider, res.address.type) && provider.normalizeDesiredResource) {
const remote = await provider.readComparableResource?.(res.address.type, res.remote_id, res.address.name);
const decl = options.config ? getResourceDeclaration(res.address, options.config) : null;
const remote = await provider.readComparableResource?.(
res.address.type,
res.remote_id,
res.address.name,
decl ?? undefined,
);
if (!remote) {
if (!options.quiet) {
emitRuntimeFeedback(options.onFeedback, {
Expand All @@ -74,7 +80,6 @@ export async function refreshState(
}

const remoteHash = contentHash(remote.comparable);
const decl = options.config ? getResourceDeclaration(res.address, options.config) : null;
const desiredComparable = decl
? provider.normalizeDesiredResource(res.address.type, res.address.name, decl)
: null;
Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/src/internal/providers/drift-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export async function readComparableIfSupported(
type: ResourceType,
id: string | null,
name: string,
decl?: unknown,
): Promise<ComparableRemoteResource | null> {
if (!supportsFullDrift(adapter, type)) return null;
// Invoke as a method on the adapter — extracting it into a local first would
// drop the `this` binding and silently fail for class-based adapters.
if (typeof adapter.readComparableResource !== "function") return null;
try {
return await adapter.readComparableResource(type, id, name);
return await adapter.readComparableResource(type, id, name, decl);
} catch {
return null;
}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/internal/providers/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export interface ProviderAdapter {
type: ResourceType,
id: string | null,
name: string,
decl?: unknown,
): Promise<ComparableRemoteResource | null>;
normalizeDesiredResource?(type: ResourceType, name: string, decl: unknown): unknown | null;

Expand Down
75 changes: 65 additions & 10 deletions packages/sdk/src/internal/providers/qoder/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export class QoderAdapter implements ProviderAdapter {
type: ResourceType,
id: string | null,
name: string,
decl?: unknown,
): Promise<ComparableRemoteResource | null> {
if (type !== "agent" && type !== "environment" && type !== "template" && type !== "identity" && type !== "channel")
return null;
Expand All @@ -304,15 +305,23 @@ export class QoderAdapter implements ProviderAdapter {
const comparable = this.normalizeRemote(type, raw);
return { id: remote.id, type, comparable, snapshot: comparable };
}
const isTemplate = type === "template";
const endpoint = type === "agent" ? "/agents" : type === "environment" ? "/environments" : "/templates";
const raw = await locateRemote(
isTemplate ? this.forwardClient : this.client,
endpoint,
name,
id,
isTemplate ? (item) => item.status !== "archived" : notArchived,
);

let raw: Record<string, unknown> | null;
if ((type === "agent" || type === "environment") && decl !== undefined && this.projectName) {
const declaredName = (decl as { name?: unknown }).name;
const displayName = typeof declaredName === "string" ? declaredName : name;
raw = await this.readManagedResourceByIdentity(type, id, displayName);
} else {
const isTemplate = type === "template";
const endpoint = type === "agent" ? "/agents" : type === "environment" ? "/environments" : "/templates";
raw = await locateRemote(
isTemplate ? this.forwardClient : this.client,
endpoint,
name,
id,
isTemplate ? (item) => item.status !== "archived" : notArchived,
);
}
if (!raw) return null;

const comparable = this.normalizeRemote(type, raw);
Expand All @@ -325,6 +334,52 @@ export class QoderAdapter implements ProviderAdapter {
};
}

private async readManagedResourceByIdentity(
type: "agent" | "environment",
id: string | null,
displayName: string,
): Promise<Record<string, unknown> | null> {
const endpoint = type === "agent" ? "/agents" : "/environments";
if (id) {
try {
const raw = (await this.client.get(`${endpoint}/${id}`)) as Record<string, unknown>;
return this.matchesManagedResourceIdentity(type, raw, displayName) ? raw : null;
} catch (error) {
if (ApiError.isNotFound(error)) return null;
throw error;
}
}

const matches = (await this.client.getAllPaged(endpoint)).filter((raw) =>
this.matchesManagedResourceIdentity(type, raw, displayName),
);
if (matches.length !== 1) return null;
const matchedId = matches[0]?.id;
if (typeof matchedId !== "string") return null;
try {
const raw = (await this.client.get(`${endpoint}/${matchedId}`)) as Record<string, unknown>;
return this.matchesManagedResourceIdentity(type, raw, displayName) ? raw : null;
} catch (error) {
if (ApiError.isNotFound(error)) return null;
throw error;
}
}

private matchesManagedResourceIdentity(
type: "agent" | "environment",
raw: Record<string, unknown>,
displayName: string,
): boolean {
if (!notArchived(raw) || raw.name !== displayName) return false;
if (typeof raw.type === "string" && raw.type !== type) return false;
const metadata = raw.metadata;
if (!metadata || typeof metadata !== "object") return false;
return (
(metadata as Record<string, unknown>)["agents.project"] === this.projectName &&
(metadata as Record<string, unknown>)["agents.resource"] === displayName
);
}

normalizeDesiredResource(type: ResourceType, name: string, decl: unknown): unknown | null {
if (type === "environment") {
return this.normalizeRemote(
Expand Down Expand Up @@ -410,7 +465,7 @@ export class QoderAdapter implements ProviderAdapter {

return compactDeep({
name: raw.name,
description: raw.description,
description: raw.description === "" ? undefined : raw.description,
model: normalizeModel(raw.model),
instructions: raw.system,
tools: normalizeQoderTools(raw.tools),
Expand Down
12 changes: 7 additions & 5 deletions packages/sdk/src/internal/providers/qoder/mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { resolveSandboxMountPath } from "../../utils/sandbox-mount.ts";
import { permissionOverridesFromWire, resolveBuiltinTools, toPermissionPolicy } from "../../utils/tool-permissions.ts";
import type { ResolvedAgentRefs, ResolvedDeploymentRefs, ResolvedTemplateRefs } from "../interface.ts";
import { mapGithubRepositorySessionResource, resolveGithubRepositoryMountPath } from "../session-resource-mapper.ts";
import { injectMetadata, secretPlaceholder, slug } from "../sync-mapping.ts";
import { injectManagedResourceMetadata, injectMetadata, secretPlaceholder, slug } from "../sync-mapping.ts";

// Qoder's API expects builtin tool names in PascalCase. The configuration layer
// (agents.yaml / playbook JSON) uses snake_case or lowercase aliases and is
Expand Down Expand Up @@ -54,6 +54,7 @@ function normalizeEnvironmentPackages(value: unknown): Record<string, string[]>
}

export function mapEnvironment(name: string, decl: EnvironmentDecl, projectName: string): unknown {
const displayName = decl.name ?? name;
const envType = decl.config.type ?? "cloud";
const config: Record<string, unknown> = { type: envType };
if (decl.config.networking) config.networking = decl.config.networking;
Expand All @@ -62,10 +63,10 @@ export function mapEnvironment(name: string, decl: EnvironmentDecl, projectName:
if (packages) config.packages = packages;
if (decl.config.setup_script !== undefined) config.setup_script = decl.config.setup_script;
return {
name,
name: displayName,
description: decl.description ?? "",
config,
metadata: injectMetadata(decl.metadata, projectName, name),
metadata: injectManagedResourceMetadata(decl.metadata, projectName, displayName),
};
}

Expand Down Expand Up @@ -410,16 +411,17 @@ export function mapAgent(
model = typeof qoderModel === "string" ? qoderModel : qoderModel.id;
}

const displayName = decl.name ?? name;
const body: Record<string, unknown> = {
name: decl.name ?? name,
name: displayName,
model,
system: decl.instructions,
};

if (version !== undefined) body.version = version;
if (decl.description) body.description = decl.description;
if (projectName) {
body.metadata = injectMetadata(decl.metadata, projectName, name);
body.metadata = injectManagedResourceMetadata(decl.metadata, projectName, displayName);
} else if (decl.metadata) {
body.metadata = decl.metadata;
}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/internal/providers/resource-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export interface DriftReadAdapter {
type: ResourceType,
id: string | null,
name: string,
decl?: unknown,
): Promise<ComparableRemoteResource | null>;
normalizeDesiredResource?(type: ResourceType, name: string, decl: unknown): unknown | null;
}
Loading